diff --git a/commands/audit/audit.go b/commands/audit/audit.go index 19d5f73c..04530126 100644 --- a/commands/audit/audit.go +++ b/commands/audit/audit.go @@ -8,7 +8,6 @@ import ( "github.com/jfrog/jfrog-cli-security/scangraph" "github.com/jfrog/jfrog-cli-security/utils" clientutils "github.com/jfrog/jfrog-client-go/utils" - "github.com/jfrog/jfrog-client-go/utils/log" "github.com/jfrog/jfrog-client-go/xray" "github.com/jfrog/jfrog-client-go/xray/services" "golang.org/x/sync/errgroup" @@ -161,7 +160,7 @@ func RunAudit(auditParams *AuditParams) (results *xrayutils.Results, err error) return } results.XrayVersion = auditParams.xrayVersion - results.ExtendedScanResults.EntitledForJas, err = isEntitledForJas(xrayManager, auditParams.xrayVersion) + results.ExtendedScanResults.EntitledForJas, err = xrayutils.IsEntitledForJas(xrayManager, auditParams.xrayVersion) if err != nil { return } @@ -186,12 +185,3 @@ func RunAudit(auditParams *AuditParams) (results *xrayutils.Results, err error) } return } - -func isEntitledForJas(xrayManager *xray.XrayServicesManager, xrayVersion string) (entitled bool, err error) { - if e := clientutils.ValidateMinimumVersion(clientutils.Xray, xrayVersion, xrayutils.EntitlementsMinVersion); e != nil { - log.Debug(e) - return - } - entitled, err = xrayManager.IsEntitled(xrayutils.ApplicabilityFeatureId) - return -} diff --git a/commands/audit/jas/applicability/applicabilitymanager.go b/commands/audit/jas/applicability/applicabilitymanager.go index a727f1fd..0004c738 100644 --- a/commands/audit/jas/applicability/applicabilitymanager.go +++ b/commands/audit/jas/applicability/applicabilitymanager.go @@ -17,9 +17,10 @@ import ( ) const ( - applicabilityScanType = "analyze-applicability" - applicabilityScanCommand = "ca" - applicabilityDocsUrlSuffix = "contextual-analysis" + applicabilityScanType = "analyze-applicability" + applicabilityScanCommand = "ca" + applicabilityDocsUrlSuffix = "contextual-analysis" + applicabilityDockerScanScanType = "analyze-applicability-docker-scan" ) type ApplicabilityScanManager struct { @@ -29,6 +30,7 @@ type ApplicabilityScanManager struct { xrayResults []services.ScanResponse scanner *jas.JasScanner thirdPartyScan bool + commandType string } // The getApplicabilityScanResults function runs the applicability scan flow, which includes the following steps: @@ -55,6 +57,37 @@ func RunApplicabilityScan(xrayResults []services.ScanResponse, directDependencie return } +// The getApplicabilityScanResults function runs the applicability scan flow, which includes the following steps: +// Creating an ApplicabilityScanManager object. +// Checking if the scanned project is eligible for applicability scan. +// Running the analyzer manager executable. +// Parsing the analyzer manager results. +// Return values: +// map[string]string: A map containing the applicability result of each XRAY CVE. +// bool: true if the user is entitled to the applicability scan, false otherwise. +// error: An error object (if any). +func RunApplicabilityWithScanCves(xrayResults []services.ScanResponse, cveList []string, + scannedTechnologies []coreutils.Technology, scanner *jas.JasScanner) (results []*sarif.Run, err error) { + applicabilityScanManager := newApplicabilityScanManagerCves(xrayResults, cveList, scanner) + if err = applicabilityScanManager.scanner.Run(applicabilityScanManager); err != nil { + err = utils.ParseAnalyzerManagerError(utils.Applicability, err) + return + } + results = applicabilityScanManager.applicabilityScanResults + return +} + +func newApplicabilityScanManagerCves(xrayScanResults []services.ScanResponse, cveList []string, scanner *jas.JasScanner) (manager *ApplicabilityScanManager) { + return &ApplicabilityScanManager{ + applicabilityScanResults: []*sarif.Run{}, + directDependenciesCves: cveList, + xrayResults: xrayScanResults, + scanner: scanner, + thirdPartyScan: false, + commandType: applicabilityDockerScanScanType, + } +} + func newApplicabilityScanManager(xrayScanResults []services.ScanResponse, directDependencies []string, scanner *jas.JasScanner, thirdPartyScan bool) (manager *ApplicabilityScanManager) { directDependenciesCves, indirectDependenciesCves := extractDependenciesCvesFromScan(xrayScanResults, directDependencies) return &ApplicabilityScanManager{ @@ -64,6 +97,7 @@ func newApplicabilityScanManager(xrayScanResults []services.ScanResponse, direct xrayResults: xrayScanResults, scanner: scanner, thirdPartyScan: thirdPartyScan, + commandType: applicabilityScanType, } } @@ -152,6 +186,7 @@ type scanConfiguration struct { CveWhitelist []string `yaml:"cve-whitelist"` IndirectCveWhitelist []string `yaml:"indirect-cve-whitelist"` SkippedDirs []string `yaml:"skipped-folders"` + ScanType string `yaml:"scantype"` } func (asm *ApplicabilityScanManager) createConfigFile(module jfrogappsconfig.Module) error { @@ -169,7 +204,7 @@ func (asm *ApplicabilityScanManager) createConfigFile(module jfrogappsconfig.Mod { Roots: roots, Output: asm.scanner.ResultsFileName, - Type: applicabilityScanType, + Type: asm.commandType, GrepDisable: false, CveWhitelist: asm.directDependenciesCves, IndirectCveWhitelist: asm.indirectDependenciesCves, diff --git a/commands/audit/jas/applicability/applicabilitymanager_test.go b/commands/audit/jas/applicability/applicabilitymanager_test.go index 672f8623..ffba502f 100644 --- a/commands/audit/jas/applicability/applicabilitymanager_test.go +++ b/commands/audit/jas/applicability/applicabilitymanager_test.go @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" ) +// TODO: ADD var mockDirectDependencies = []string{"issueId_2_direct_dependency", "issueId_1_direct_dependency"} var mockMultiRootDirectDependencies = []string{"issueId_2_direct_dependency", "issueId_1_direct_dependency", "issueId_3_direct_dependency", "issueId_4_direct_dependency"} diff --git a/commands/audit/jas/secrets/secretsscanner.go b/commands/audit/jas/secrets/secretsscanner.go index ca9d2ce7..2c1dab49 100644 --- a/commands/audit/jas/secrets/secretsscanner.go +++ b/commands/audit/jas/secrets/secretsscanner.go @@ -12,14 +12,16 @@ import ( ) const ( - secretsScanCommand = "sec" - secretsScannerType = "secrets-scan" - secretsDocsUrlSuffix = "secrets" + secretsScanCommand = "sec" + SecretsScannerType = "secrets-scan" // #nosec + SecretsScannerDockerScanType = "secrets-docker-scan" // #nosec + secretsDocsUrlSuffix = "secrets" ) type SecretScanManager struct { secretsScannerResults []*sarif.Run scanner *jas.JasScanner + scanType string } // The getSecretsScanResults function runs the secrets scan flow, which includes the following steps: @@ -29,8 +31,8 @@ type SecretScanManager struct { // Return values: // []utils.IacOrSecretResult: a list of the secrets that were found. // error: An error object (if any). -func RunSecretsScan(scanner *jas.JasScanner) (results []*sarif.Run, err error) { - secretScanManager := newSecretsScanManager(scanner) +func RunSecretsScan(scanner *jas.JasScanner, scanType string) (results []*sarif.Run, err error) { + secretScanManager := newSecretsScanManager(scanner, scanType) log.Info("Running secrets scanning...") if err = secretScanManager.scanner.Run(secretScanManager); err != nil { err = utils.ParseAnalyzerManagerError(utils.Secrets, err) @@ -43,10 +45,11 @@ func RunSecretsScan(scanner *jas.JasScanner) (results []*sarif.Run, err error) { return } -func newSecretsScanManager(scanner *jas.JasScanner) (manager *SecretScanManager) { +func newSecretsScanManager(scanner *jas.JasScanner, scanType string) (manager *SecretScanManager) { return &SecretScanManager{ secretsScannerResults: []*sarif.Run{}, scanner: scanner, + scanType: scanType, } } @@ -89,7 +92,7 @@ func (s *SecretScanManager) createConfigFile(module jfrogappsconfig.Module) erro { Roots: roots, Output: s.scanner.ResultsFileName, - Type: secretsScannerType, + Type: s.scanType, SkippedDirs: jas.GetExcludePatterns(module, module.Scanners.Secrets), }, }, diff --git a/commands/audit/jas/secrets/secretsscanner_test.go b/commands/audit/jas/secrets/secretsscanner_test.go index 82f10c99..47ed4d10 100644 --- a/commands/audit/jas/secrets/secretsscanner_test.go +++ b/commands/audit/jas/secrets/secretsscanner_test.go @@ -12,10 +12,11 @@ import ( "github.com/stretchr/testify/assert" ) +// TODO: Add docker scan applicablity and seacrets func TestNewSecretsScanManager(t *testing.T) { scanner, cleanUp := jas.InitJasTest(t) defer cleanUp() - secretScanManager := newSecretsScanManager(scanner) + secretScanManager := newSecretsScanManager(scanner, SecretsScannerType) assert.NotEmpty(t, secretScanManager) assert.NotEmpty(t, secretScanManager.scanner.ConfigFileName) @@ -26,7 +27,7 @@ func TestNewSecretsScanManager(t *testing.T) { func TestSecretsScan_CreateConfigFile_VerifyFileWasCreated(t *testing.T) { scanner, cleanUp := jas.InitJasTest(t) defer cleanUp() - secretScanManager := newSecretsScanManager(scanner) + secretScanManager := newSecretsScanManager(scanner, SecretsScannerType) currWd, err := coreutils.GetWorkingDirectory() assert.NoError(t, err) @@ -53,7 +54,7 @@ func TestRunAnalyzerManager_ReturnsGeneralError(t *testing.T) { scanner, cleanUp := jas.InitJasTest(t) defer cleanUp() - secretScanManager := newSecretsScanManager(scanner) + secretScanManager := newSecretsScanManager(scanner, SecretsScannerType) assert.Error(t, secretScanManager.runAnalyzerManager()) } @@ -61,7 +62,7 @@ func TestParseResults_EmptyResults(t *testing.T) { scanner, cleanUp := jas.InitJasTest(t) defer cleanUp() // Arrange - secretScanManager := newSecretsScanManager(scanner) + secretScanManager := newSecretsScanManager(scanner, SecretsScannerType) secretScanManager.scanner.ResultsFileName = filepath.Join(jas.GetTestDataPath(), "secrets-scan", "no-secrets.sarif") // Act @@ -84,7 +85,7 @@ func TestParseResults_ResultsContainSecrets(t *testing.T) { scanner, cleanUp := jas.InitJasTest(t) defer cleanUp() - secretScanManager := newSecretsScanManager(scanner) + secretScanManager := newSecretsScanManager(scanner, SecretsScannerType) secretScanManager.scanner.ResultsFileName = filepath.Join(jas.GetTestDataPath(), "secrets-scan", "contain-secrets.sarif") // Act @@ -107,7 +108,7 @@ func TestGetSecretsScanResults_AnalyzerManagerReturnsError(t *testing.T) { scanner, cleanUp := jas.InitJasTest(t) defer cleanUp() - secretsResults, err := RunSecretsScan(scanner) + secretsResults, err := RunSecretsScan(scanner, SecretsScannerType) assert.Error(t, err) assert.ErrorContains(t, err, "failed to run Secrets scan") diff --git a/commands/audit/jasrunner.go b/commands/audit/jasrunner.go index 434d9044..7cf7d98a 100644 --- a/commands/audit/jasrunner.go +++ b/commands/audit/jasrunner.go @@ -2,6 +2,7 @@ package audit import ( "errors" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-security/commands/audit/jas" "github.com/jfrog/jfrog-cli-security/commands/audit/jas/applicability" @@ -41,7 +42,7 @@ func runJasScannersAndSetResults(scanResults *utils.Results, directDependencies if progress != nil { progress.SetHeadlineMsg("Running secrets scanning") } - scanResults.ExtendedScanResults.SecretsScanResults, err = secrets.RunSecretsScan(scanner) + scanResults.ExtendedScanResults.SecretsScanResults, err = secrets.RunSecretsScan(scanner, secrets.SecretsScannerType) if err != nil { return } diff --git a/commands/scan/jasrunner_cves.go b/commands/scan/jasrunner_cves.go new file mode 100644 index 00000000..89e9a6c9 --- /dev/null +++ b/commands/scan/jasrunner_cves.go @@ -0,0 +1,45 @@ +package scan + +import ( + "errors" + "fmt" + + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-security/commands/audit/jas" + "github.com/jfrog/jfrog-cli-security/commands/audit/jas/applicability" + "github.com/jfrog/jfrog-cli-security/commands/audit/jas/secrets" + + "github.com/jfrog/jfrog-cli-security/utils" + "github.com/jfrog/jfrog-client-go/utils/log" +) + +func runJasScannersAndSetResults(scanResults *utils.Results, cveList []string, + serverDetails *config.ServerDetails, workingDirs []string) (err error) { + + if serverDetails == nil || len(serverDetails.Url) == 0 { + log.Warn("To include 'Advanced Security' scan as part of the audit output, please run the 'jf c add' command before running this command.") + return + } + multiScanId := "" // Also empty for audit + scanner, err := jas.NewJasScanner(workingDirs, serverDetails, multiScanId) + if err != nil { + return + } + + defer func() { + cleanup := scanner.ScannerDirCleanupFunc + err = errors.Join(err, cleanup()) + }() + + scanResults.ExtendedScanResults.ApplicabilityScanResults, err = applicability.RunApplicabilityWithScanCves(scanResults.GetScaScansXrayResults(), cveList, scanResults.GetScaScannedTechnologies(), scanner) + if err != nil { + fmt.Println("there was an error:", err) + return + } + + scanResults.ExtendedScanResults.SecretsScanResults, err = secrets.RunSecretsScan(scanner, secrets.SecretsScannerDockerScanType) + if err != nil { + return + } + return +} diff --git a/commands/scan/scan.go b/commands/scan/scan.go index 535d81f9..2c4e14d2 100644 --- a/commands/scan/scan.go +++ b/commands/scan/scan.go @@ -10,12 +10,14 @@ import ( "regexp" "strings" + "golang.org/x/exp/slices" + "github.com/jfrog/jfrog-cli-security/scangraph" xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" + "golang.org/x/sync/errgroup" "github.com/jfrog/gofrog/parallel" "github.com/jfrog/jfrog-cli-core/v2/common/format" - outputFormat "github.com/jfrog/jfrog-cli-core/v2/common/format" "github.com/jfrog/jfrog-cli-core/v2/common/spec" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" @@ -46,7 +48,7 @@ type ScanCommand struct { // The location of the downloaded Xray indexer binary on the local file system. indexerPath string indexerTempDir string - outputFormat outputFormat.OutputFormat + outputFormat format.OutputFormat projectKey string minSeverityFilter string watches []string @@ -78,7 +80,7 @@ func (scanCmd *ScanCommand) SetThreads(threads int) *ScanCommand { return scanCmd } -func (scanCmd *ScanCommand) SetOutputFormat(format outputFormat.OutputFormat) *ScanCommand { +func (scanCmd *ScanCommand) SetOutputFormat(format format.OutputFormat) *ScanCommand { scanCmd.outputFormat = format return scanCmd } @@ -249,6 +251,23 @@ func (scanCmd *ScanCommand) Run() (err error) { scanResults.XrayVersion = xrayVersion scanResults.ScaResults = []xrutils.ScaScanResult{{XrayResults: flatResults}} + scanResults.ExtendedScanResults.EntitledForJas, err = xrutils.IsEntitledForJas(xrayManager, xrayVersion) + errGroup := new(errgroup.Group) + if scanResults.ExtendedScanResults.EntitledForJas { + // Download (if needed) the analyzer manager in a background routine. + errGroup.Go(xrutils.DownloadAnalyzerManagerIfNeeded) + } + // Wait for the Download of the AnalyzerManager to complete. + if err = errGroup.Wait(); err != nil { + err = errors.New("failed while trying to get Analyzer Manager: " + err.Error()) + } + + if scanResults.ExtendedScanResults.EntitledForJas { + cveList := cveListFromVulnerabilities(flatResults) + workingDirs := []string{scanCmd.spec.Files[0].Pattern} + scanResults.JasError = runJasScannersAndSetResults(scanResults, cveList, scanCmd.serverDetails, workingDirs) + } + if err = xrutils.NewResultsWriter(scanResults). SetOutputFormat(scanCmd.outputFormat). SetIncludeVulnerabilities(scanCmd.includeVulnerabilities). @@ -454,6 +473,20 @@ func appendErrorSlice(scanErrors []formats.SimpleJsonError, errorsToAdd [][]form return scanErrors } -func ConditionalUploadDefaultScanFunc(serverDetails *config.ServerDetails, fileSpec *spec.SpecFiles, threads int, scanOutputFormat format.OutputFormat) error { - return NewScanCommand().SetServerDetails(serverDetails).SetSpec(fileSpec).SetThreads(threads).SetOutputFormat(scanOutputFormat).SetFail(true).SetPrintExtendedTable(false).Run() +func cveListFromVulnerabilities(flatResults []services.ScanResponse) []string { + var cveList []string + var technologiesList []string + for _, result := range flatResults { + for _, vulnerability := range result.Vulnerabilities { + for _, cve := range vulnerability.Cves { + if !slices.Contains(cveList, cve.Id) && (cve.Id != "") { + cveList = append(cveList, cve.Id) + } + } + if !slices.Contains(technologiesList, vulnerability.Technology) && (vulnerability.Technology != "") { + technologiesList = append(technologiesList, vulnerability.Technology) + } + } + } + return cveList } diff --git a/formats/conversion.go b/formats/conversion.go index 1a360ec1..8a602869 100644 --- a/formats/conversion.go +++ b/formats/conversion.go @@ -28,6 +28,7 @@ func ConvertToVulnerabilityScanTableRow(rows []VulnerabilityOrViolationRow) (tab tableRows = append(tableRows, vulnerabilityScanTableRow{ severity: rows[i].Severity, severityNumValue: rows[i].SeverityNumValue, + applicable: rows[i].Applicable, impactedPackageName: rows[i].ImpactedDependencyName, impactedPackageVersion: rows[i].ImpactedDependencyVersion, ImpactedPackageType: rows[i].ImpactedDependencyType, diff --git a/formats/table.go b/formats/table.go index 8dd71d1d..fc9486d9 100644 --- a/formats/table.go +++ b/formats/table.go @@ -20,7 +20,8 @@ type vulnerabilityTableRow struct { } type vulnerabilityScanTableRow struct { - severity string `col-name:"Severity"` + severity string `col-name:"Severity"` + applicable string `col-name:"Contextual\nAnalysis" omitempty:"true"` // For sorting severityNumValue int directPackages []directPackagesTableRow `embed-table:"true"` diff --git a/tests/testdata/other/applicability-scan/applicable-docker-scan-cve-results.sarif b/tests/testdata/other/applicability-scan/applicable-docker-scan-cve-results.sarif new file mode 100644 index 00000000..30404ce4 --- /dev/null +++ b/tests/testdata/other/applicability-scan/applicable-docker-scan-cve-results.sarif @@ -0,0 +1 @@ +TODO \ No newline at end of file diff --git a/utils/jasutils.go b/utils/jasutils.go new file mode 100644 index 00000000..0aa92ea2 --- /dev/null +++ b/utils/jasutils.go @@ -0,0 +1,16 @@ +package utils + +import ( + clientutils "github.com/jfrog/jfrog-client-go/utils" + "github.com/jfrog/jfrog-client-go/utils/log" + "github.com/jfrog/jfrog-client-go/xray" +) + +func IsEntitledForJas(xrayManager *xray.XrayServicesManager, xrayVersion string) (entitled bool, err error) { + if e := clientutils.ValidateMinimumVersion(clientutils.Xray, xrayVersion, EntitlementsMinVersion); e != nil { + log.Debug(e) + return + } + entitled, err = xrayManager.IsEntitled(ApplicabilityFeatureId) + return +}