From 5539ea48cd5d996f660dc67cf34aad51dcf37d92 Mon Sep 17 00:00:00 2001 From: yahavi Date: Tue, 5 Sep 2023 17:14:19 +0300 Subject: [PATCH] Support JFrog Apps Config file --- go.mod | 3 +- go.sum | 8 +- .../jas/applicability/applicabilitymanager.go | 17 ++- .../applicabilitymanager_test.go | 2 +- xray/commands/audit/jas/common.go | 76 ++++++++++-- xray/commands/audit/jas/common_test.go | 113 +++++++++++++++++- xray/commands/audit/jas/iac/iacscanner.go | 20 ++-- .../commands/audit/jas/iac/iacscanner_test.go | 12 +- xray/commands/audit/jas/sast/sastscanner.go | 45 ++++++- .../audit/jas/sast/sastscanner_test.go | 6 +- .../audit/jas/secrets/secretsscanner.go | 24 ++-- .../audit/jas/secrets/secretsscanner_test.go | 10 +- .../jas/testdata/.jfrog/jfrog-apps-config.yml | 50 ++++++++ xray/utils/analyzermanager.go | 7 +- 14 files changed, 335 insertions(+), 58 deletions(-) create mode 100644 xray/commands/audit/jas/testdata/.jfrog/jfrog-apps-config.yml diff --git a/go.mod b/go.mod index 891def452..cf334f972 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.9 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 @@ -94,7 +95,7 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect ) -replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20230831152946-6ed2ae1aa57f +replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20230906115540-2c3c91d271d6 replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go v1.8.9-0.20230905120411-62d1bdd4eb38 diff --git a/go.sum b/go.sum index a028a4cfc..c05d1c7f1 100644 --- a/go.sum +++ b/go.sum @@ -198,8 +198,10 @@ github.com/jfrog/build-info-go v1.8.9-0.20230905120411-62d1bdd4eb38 h1:XyAcwWP2a github.com/jfrog/build-info-go v1.8.9-0.20230905120411-62d1bdd4eb38/go.mod h1:QEskae5fQpjeY2PBzsjWtUQVskYSNDF2sSmw/Gx44dQ= 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-client-go v1.28.1-0.20230831152946-6ed2ae1aa57f h1:S6l0o2sKFLRJ+QYVB5U/PJhrnwFSmKFFY7eHpRPRH8A= -github.com/jfrog/jfrog-client-go v1.28.1-0.20230831152946-6ed2ae1aa57f/go.mod h1:uUnMrqHX7Xi+OCaZEE4b3BtsmGeOSCB7XqaEWVXEH/E= +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.20230906115540-2c3c91d271d6 h1:9mNCAUu/uHx80s4rMc9PeI1lllrZ1MOPUesIMglFoTY= +github.com/jfrog/jfrog-client-go v1.28.1-0.20230906115540-2c3c91d271d6/go.mod h1:soD5VL3X+G+0KKUNSlb0CSdF9nwHsQZCr0xqOGedAHM= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= @@ -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 cbab69833..abb80052a 100644 --- a/xray/commands/audit/jas/applicability/applicabilitymanager.go +++ b/xray/commands/audit/jas/applicability/applicabilitymanager.go @@ -1,10 +1,12 @@ package applicability import ( - "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/jas" "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/gofrog/datastructures" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-cli-core/v2/xray/utils" @@ -99,13 +101,16 @@ 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 { @@ -151,7 +156,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 76a0e567c..91135162c 100644 --- a/xray/commands/audit/jas/applicability/applicabilitymanager_test.go +++ b/xray/commands/audit/jas/applicability/applicabilitymanager_test.go @@ -37,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) diff --git a/xray/commands/audit/jas/common.go b/xray/commands/audit/jas/common.go index 30fac5bc7..21e37fb76 100644 --- a/xray/commands/audit/jas/common.go +++ b/xray/commands/audit/jas/common.go @@ -2,24 +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" - "os" - "path/filepath" - "strings" - "testing" ) var ( - SkippedDirs = []string{"**/*test*/**", "**/*venv*/**", "**/*node_modules*/**", "**/*target*/**"} + DefaultExcludePatterns = []string{"**/*test*/**", "**/*venv*/**", "**/*node_modules*/**", "**/*target*/**"} ) type JasScanner struct { @@ -27,7 +32,7 @@ type JasScanner struct { ResultsFileName string AnalyzerManager utils.AnalyzerManager ServerDetails *config.ServerDetails - WorkingDirs []string + JFrogAppsConfig *jfrogappsconfig.JFrogAppsConfig ScannerDirCleanupFunc func() error } @@ -46,21 +51,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 } }() @@ -180,3 +205,34 @@ 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 { + if scanner == nil || len(scanner.WorkingDirs) == 0 { + return []string{module.SourceRoot} + } + var roots []string + for _, workingDir := range scanner.WorkingDirs { + roots = append(roots, filepath.Join(module.SourceRoot, workingDir)) + } + return roots +} + +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/common_test.go b/xray/commands/audit/jas/common_test.go index 305629f31..6a4ccfa6a 100644 --- a/xray/commands/audit/jas/common_test.go +++ b/xray/commands/audit/jas/common_test.go @@ -1,8 +1,15 @@ package jas import ( - "github.com/stretchr/testify/assert" + "fmt" + "os" + "path/filepath" "testing" + + jfrogappsconfig "github.com/jfrog/jfrog-apps-config/go" + "github.com/jfrog/jfrog-cli-core/v2/xray/utils" + clientTestUtils "github.com/jfrog/jfrog-client-go/utils/tests" + "github.com/stretchr/testify/assert" ) func TestHideSecret(t *testing.T) { @@ -21,3 +28,107 @@ func TestHideSecret(t *testing.T) { assert.Equal(t, test.expectedOutput, hideSecret(test.secret)) } } + +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) { + module := jfrogappsconfig.Module{SourceRoot: "source-root"} + for _, testCase := range getSourceRootsCases { + t.Run("", func(t *testing.T) { + scanner := testCase.scanner + actualSourceRoots := GetSourceRoots(module, scanner) + 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) + }) + } +} diff --git a/xray/commands/audit/jas/iac/iacscanner.go b/xray/commands/audit/jas/iac/iacscanner.go index c4fe18062..f47016273 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" ) @@ -47,16 +49,18 @@ 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 } var workingDirResults []utils.SourceCodeScanResult - if workingDirResults, err = jas.GetSourceCodeScanResults(scanner.ResultsFileName, wd, utils.IaC); err != nil { + if workingDirResults, err = jas.GetSourceCodeScanResults(iac.scanner.ResultsFileName, module.SourceRoot, utils.IaC); err != nil { return } iac.iacScannerResults = append(iac.iacScannerResults, workingDirResults...) @@ -74,14 +78,14 @@ type iacScanConfiguration struct { SkippedDirs []string `yaml:"skipped-folders"` } -func (iac *IacScanManager) createConfigFile(currentWd string) error { +func (iac *IacScanManager) createConfigFile(module jfrogappsconfig.Module) error { configFileContent := iacScanConfig{ Scans: []iacScanConfiguration{ { - Roots: []string{currentWd}, + Roots: jas.GetSourceRoots(module, module.Scanners.Iac), 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 a43fef61a..5ef4e5e55 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/jfrog/jfrog-cli-core/v2/xray/utils" "github.com/stretchr/testify/assert" @@ -21,7 +23,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) } } @@ -34,7 +36,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) @@ -58,7 +60,7 @@ func TestIacParseResults_EmptyResults(t *testing.T) { // Act var err error - iacScanManager.iacScannerResults, err = jas.GetSourceCodeScanResults(iacScanManager.scanner.ResultsFileName, scanner.WorkingDirs[0], utils.IaC) + iacScanManager.iacScannerResults, err = jas.GetSourceCodeScanResults(iacScanManager.scanner.ResultsFileName, scanner.JFrogAppsConfig.Modules[0].SourceRoot, utils.IaC) // Assert assert.NoError(t, err) @@ -74,7 +76,7 @@ func TestIacParseResults_ResultsContainIacViolations(t *testing.T) { // Act var err error - iacScanManager.iacScannerResults, err = jas.GetSourceCodeScanResults(iacScanManager.scanner.ResultsFileName, scanner.WorkingDirs[0], utils.IaC) + iacScanManager.iacScannerResults, err = jas.GetSourceCodeScanResults(iacScanManager.scanner.ResultsFileName, scanner.JFrogAppsConfig.Modules[0].SourceRoot, utils.IaC) // Assert assert.NoError(t, err) diff --git a/xray/commands/audit/jas/sast/sastscanner.go b/xray/commands/audit/jas/sast/sastscanner.go index dc31fa9ea..1aa7c954b 100644 --- a/xray/commands/audit/jas/sast/sastscanner.go +++ b/xray/commands/audit/jas/sast/sastscanner.go @@ -1,6 +1,7 @@ package sast import ( + 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" @@ -36,19 +37,53 @@ func newSastScanManager(scanner *jas.JasScanner) (manager *SastScanManager) { } } -func (ssm *SastScanManager) Run(wd string) (err error) { - scanner := ssm.scanner - if err = ssm.runAnalyzerManager(wd); err != nil { +func (ssm *SastScanManager) Run(module jfrogappsconfig.Module) (err error) { + if jas.ShouldSkipScanner(module, utils.Sast) { + return + } + if err = ssm.createConfigFile(module); err != nil { + return + } + if err = ssm.runAnalyzerManager(module.SourceRoot); err != nil { return } var workingDirResults []utils.SourceCodeScanResult - if workingDirResults, err = jas.GetSourceCodeScanResults(scanner.ResultsFileName, wd, utils.Sast); err != nil { + if workingDirResults, err = jas.GetSourceCodeScanResults(ssm.scanner.ResultsFileName, module.SourceRoot, utils.Sast); err != nil { return } ssm.sastScannerResults = append(ssm.sastScannerResults, workingDirResults...) return } +type sastScanConfig struct { + Scans []scanConfiguration `yaml:"scans,omitempty"` +} + +type scanConfiguration struct { + Roots []string `yaml:"roots,omitempty"` + Languages []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{} + } + configFileContent := sastScanConfig{ + Scans: []scanConfiguration{ + { + Roots: jas.GetSourceRoots(module, &sastScanner.Scanner), + Languages: []string{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.ResultsFileName, sastScanCommand, wd, ssm.scanner.ResultsFileName, ssm.scanner.ServerDetails) } diff --git a/xray/commands/audit/jas/sast/sastscanner_test.go b/xray/commands/audit/jas/sast/sastscanner_test.go index 969ab80ce..2fc83fe4d 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.GetSourceCodeScanResults(sastScanManager.scanner.ResultsFileName, scanner.WorkingDirs[0], utils.Sast) + sastScanManager.sastScannerResults, err = jas.GetSourceCodeScanResults(sastScanManager.scanner.ResultsFileName, scanner.JFrogAppsConfig.Modules[0].SourceRoot, utils.Sast) // Assert assert.NoError(t, err) @@ -50,7 +50,7 @@ func TestSastParseResults_ResultsContainIacViolations(t *testing.T) { // Act var err error - sastScanManager.sastScannerResults, err = jas.GetSourceCodeScanResults(sastScanManager.scanner.ResultsFileName, scanner.WorkingDirs[0], utils.Sast) + sastScanManager.sastScannerResults, err = jas.GetSourceCodeScanResults(sastScanManager.scanner.ResultsFileName, scanner.JFrogAppsConfig.Modules[0].SourceRoot, utils.Sast) // Assert assert.NoError(t, err) diff --git a/xray/commands/audit/jas/secrets/secretsscanner.go b/xray/commands/audit/jas/secrets/secretsscanner.go index ef6722ec2..c05b14341 100644 --- a/xray/commands/audit/jas/secrets/secretsscanner.go +++ b/xray/commands/audit/jas/secrets/secretsscanner.go @@ -1,10 +1,12 @@ package secrets 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" - "path/filepath" ) const ( @@ -45,19 +47,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 = ssm.createConfigFile(module); err != nil { return } - if err = s.runAnalyzerManager(); err != nil { + if err = ssm.runAnalyzerManager(); err != nil { return } var workingDirResults []utils.SourceCodeScanResult - if workingDirResults, err = jas.GetSourceCodeScanResults(scanner.ResultsFileName, wd, utils.Secrets); err != nil { + if workingDirResults, err = jas.GetSourceCodeScanResults(ssm.scanner.ResultsFileName, module.SourceRoot, utils.Secrets); err != nil { return } - s.secretsScannerResults = append(s.secretsScannerResults, workingDirResults...) + ssm.secretsScannerResults = append(ssm.secretsScannerResults, workingDirResults...) return } @@ -72,14 +76,14 @@ type secretsScanConfiguration struct { SkippedDirs []string `yaml:"skipped-folders"` } -func (s *SecretScanManager) createConfigFile(currentWd string) error { +func (s *SecretScanManager) createConfigFile(module jfrogappsconfig.Module) error { configFileContent := secretsScanConfig{ Scans: []secretsScanConfiguration{ { - Roots: []string{currentWd}, + Roots: jas.GetSourceRoots(module, module.Scanners.Iac), 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 f403adc86..59357043a 100644 --- a/xray/commands/audit/jas/secrets/secretsscanner_test.go +++ b/xray/commands/audit/jas/secrets/secretsscanner_test.go @@ -1,11 +1,13 @@ package secrets 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/jfrog/jfrog-cli-core/v2/xray/utils" "github.com/stretchr/testify/assert" @@ -29,7 +31,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 +67,7 @@ func TestParseResults_EmptyResults(t *testing.T) { // Act var err error - secretScanManager.secretsScannerResults, err = jas.GetSourceCodeScanResults(secretScanManager.scanner.ResultsFileName, scanner.WorkingDirs[0], utils.Secrets) + secretScanManager.secretsScannerResults, err = jas.GetSourceCodeScanResults(secretScanManager.scanner.ResultsFileName, scanner.JFrogAppsConfig.Modules[0].SourceRoot, utils.Secrets) // Assert assert.NoError(t, err) @@ -82,7 +84,7 @@ func TestParseResults_ResultsContainSecrets(t *testing.T) { // Act var err error - secretScanManager.secretsScannerResults, err = jas.GetSourceCodeScanResults(secretScanManager.scanner.ResultsFileName, scanner.WorkingDirs[0], utils.Secrets) + secretScanManager.secretsScannerResults, err = jas.GetSourceCodeScanResults(secretScanManager.scanner.ResultsFileName, scanner.JFrogAppsConfig.Modules[0].SourceRoot, utils.Secrets) // Assert assert.NoError(t, err) 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 8dfda3e04..d286e2b6c 100644 --- a/xray/utils/analyzermanager.go +++ b/xray/utils/analyzermanager.go @@ -123,10 +123,15 @@ 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) + log.Debug("Executing", am.AnalyzerManagerFullPath, scanCommand, outputFile, configFile) + cmd := exec.Command(am.AnalyzerManagerFullPath, scanCommand, outputFile, configFile) defer func() { if !cmd.ProcessState.Exited() { if killProcessError := cmd.Process.Kill(); errorutils.CheckError(killProcessError) != nil {