diff --git a/go.mod b/go.mod index f4b687efc..e306e4516 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.32.3 github.com/magiconair/properties v1.8.7 github.com/manifoldco/promptui v0.9.0 diff --git a/go.sum b/go.sum index 737b839c2..8733c3d81 100644 --- a/go.sum +++ b/go.sum @@ -199,6 +199,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.32.3 h1:B2M8Gu8EMrokbHWPPDgN1b7YRWwf0oe746epvQASK6c= github.com/jfrog/jfrog-client-go v1.32.3/go.mod h1:UewnwkIf/77HzBgwCPzOHZCK6V/Nw5/JwdzN/tRb4aU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -319,7 +321,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 b038e4371..c34318c00 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" @@ -100,19 +101,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); 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 } @@ -141,25 +145,29 @@ type scanConfiguration struct { SkippedDirs []string `yaml:"skipped-folders"` } -func (asm *ApplicabilityScanManager) createConfigFile(workingDir string) error { - skipDirs := jas.SkippedDirs +func (asm *ApplicabilityScanManager) createConfigFile(module jfrogappsconfig.Module) error { + roots, err := jas.GetSourceRoots(module, nil) + if err != nil { + return err + } + excludePatterns := jas.GetExcludePatterns(module, nil) if asm.thirdPartyScan { log.Info("Including node modules folder in applicability scan") - skipDirs = removeElementFromSlice(skipDirs, jas.NodeModulesPattern) + excludePatterns = removeElementFromSlice(excludePatterns, jas.NodeModulesPattern) } configFileContent := applicabilityScanConfig{ Scans: []scanConfiguration{ { - Roots: []string{workingDir}, + Roots: roots, Output: asm.scanner.ResultsFileName, Type: applicabilityScanType, GrepDisable: false, CveWhitelist: asm.directDependenciesCves, - SkippedDirs: skipDirs, + SkippedDirs: excludePatterns, }, }, } - return jas.CreateScannersConfigFile(asm.scanner.ConfigFileName, configFileContent) + return jas.CreateScannersConfigFile(asm.scanner.ConfigFileName, configFileContent, utils.Applicability) } // Runs the analyzerManager app and returns a boolean to indicate whether the user is entitled for diff --git a/xray/commands/audit/jas/applicability/applicabilitymanager_test.go b/xray/commands/audit/jas/applicability/applicabilitymanager_test.go index b05d69130..98d54d28b 100644 --- a/xray/commands/audit/jas/applicability/applicabilitymanager_test.go +++ b/xray/commands/audit/jas/applicability/applicabilitymanager_test.go @@ -1,13 +1,15 @@ package applicability import ( + "os" + "path/filepath" + "testing" + + jfrogappsconfig "github.com/jfrog/jfrog-apps-config/go" "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 +38,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) @@ -255,7 +257,7 @@ func TestCreateConfigFile_VerifyFileWasCreated(t *testing.T) { currWd, err := coreutils.GetWorkingDirectory() assert.NoError(t, err) - err = applicabilityManager.createConfigFile(currWd) + err = applicabilityManager.createConfigFile(jfrogappsconfig.Module{SourceRoot: currWd}) assert.NoError(t, err) defer func() { @@ -280,7 +282,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) @@ -297,7 +299,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) @@ -314,7 +316,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 2dfada8dc..fa7d508ef 100644 --- a/xray/commands/audit/jas/common.go +++ b/xray/commands/audit/jas/common.go @@ -2,20 +2,24 @@ 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" ) @@ -24,7 +28,7 @@ const ( ) var ( - SkippedDirs = []string{"**/*test*/**", "**/*venv*/**", NodeModulesPattern, "**/*target*/**"} + DefaultExcludePatterns = []string{"**/*test*/**", "**/*venv*/**", NodeModulesPattern, "**/*target*/**"} mapSeverityToScore = map[string]string{ "": "0.0", @@ -41,7 +45,7 @@ type JasScanner struct { ResultsFileName string AnalyzerManager utils.AnalyzerManager ServerDetails *config.ServerDetails - WorkingDirs []string + JFrogAppsConfig *jfrogappsconfig.JFrogAppsConfig ScannerDirCleanupFunc func() error } @@ -60,22 +64,42 @@ func NewJasScanner(workingDirs []string, serverDetails *config.ServerDetails, mu 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) scanner.AnalyzerManager.MultiScanId = multiScanId 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,11 +177,12 @@ func convertToScore(severity string) string { return "" } -func CreateScannersConfigFile(fileName string, fileContent interface{}) error { +func CreateScannersConfigFile(fileName string, fileContent interface{}, scanType utils.JasScanType) error { yamlData, err := yaml.Marshal(&fileContent) if errorutils.CheckError(err) != nil { return err } + log.Debug(scanType.String() + " scanner input YAML:\n" + string(yamlData)) err = os.WriteFile(fileName, yamlData, 0644) return errorutils.CheckError(err) } @@ -196,3 +221,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..f2ea4984b 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,18 +79,22 @@ 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), }, }, } - return jas.CreateScannersConfigFile(iac.scanner.ConfigFileName, configFileContent) + return jas.CreateScannersConfigFile(iac.scanner.ConfigFileName, configFileContent, utils.IaC) } func (iac *IacScanManager) runAnalyzerManager() error { 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 d4402cd68..2052a5361 100644 --- a/xray/commands/audit/jas/sast/sastscanner.go +++ b/xray/commands/audit/jas/sast/sastscanner.go @@ -3,6 +3,9 @@ package sast import ( "fmt" + "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" @@ -11,6 +14,7 @@ import ( ) const ( + sastScannerType = "sast" sastScanCommand = "zd" ) @@ -40,12 +44,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 } @@ -54,8 +64,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, utils.Sast) +} + 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 6ed1980f3..036c028c8 100644 --- a/xray/commands/audit/jas/sast/sastscanner_test.go +++ b/xray/commands/audit/jas/sast/sastscanner_test.go @@ -21,7 +21,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) } } @@ -36,7 +36,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) { @@ -57,7 +57,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 6f0b985ff..eb383f6f4 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,18 +78,22 @@ 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), }, }, } - return jas.CreateScannersConfigFile(s.scanner.ConfigFileName, configFileContent) + return jas.CreateScannersConfigFile(s.scanner.ConfigFileName, configFileContent, utils.Secrets) } func (s *SecretScanManager) runAnalyzerManager() error { 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 12784921c..9866692de 100644 --- a/xray/utils/analyzermanager.go +++ b/xray/utils/analyzermanager.go @@ -102,10 +102,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 } - cmd := exec.Command(am.AnalyzerManagerFullPath, scanCommand, configFile, am.MultiScanId) + var cmd *exec.Cmd + if len(outputFile) > 0 { + log.Debug("Executing", am.AnalyzerManagerFullPath, scanCommand, configFile, outputFile, am.MultiScanId) + cmd = exec.Command(am.AnalyzerManagerFullPath, scanCommand, configFile, outputFile, am.MultiScanId) + } else { + log.Debug("Executing", am.AnalyzerManagerFullPath, scanCommand, configFile, am.MultiScanId) + cmd = exec.Command(am.AnalyzerManagerFullPath, scanCommand, configFile, am.MultiScanId) + } defer func() { if cmd.ProcessState != nil && !cmd.ProcessState.Exited() { if killProcessError := cmd.Process.Kill(); errorutils.CheckError(killProcessError) != nil {