diff --git a/xray/audit/jas/applicabilitymanager.go b/xray/audit/jas/applicabilitymanager.go index 373d15d6d..f4df3c875 100644 --- a/xray/audit/jas/applicabilitymanager.go +++ b/xray/audit/jas/applicabilitymanager.go @@ -30,7 +30,7 @@ const ( // bool: true if the user is entitled to the applicability scan, false otherwise. // error: An error object (if any). func getApplicabilityScanResults(xrayResults []services.ScanResponse, directDependencies []string, - scannedTechnologies []coreutils.Technology, scanner *AdvancedSecurityScanner) (results map[string]string, err error) { + scannedTechnologies []coreutils.Technology, scanner *AdvancedSecurityScanner) (results map[string]utils.ApplicabilityStatus, err error) { applicabilityScanManager := newApplicabilityScanManager(xrayResults, directDependencies, scanner) if !applicabilityScanManager.shouldRunApplicabilityScan(scannedTechnologies) { log.Debug("The technologies that have been scanned are currently not supported for contextual analysis scanning, or we couldn't find any vulnerable direct dependencies. Skipping....") @@ -45,8 +45,8 @@ func getApplicabilityScanResults(xrayResults []services.ScanResponse, directDepe } type ApplicabilityScanManager struct { - applicabilityScanResults map[string]string - directDependenciesCves *datastructures.Set[string] + applicabilityScanResults map[string]utils.ApplicabilityStatus + directDependenciesCves []string xrayResults []services.ScanResponse scanner *AdvancedSecurityScanner } @@ -54,7 +54,7 @@ type ApplicabilityScanManager struct { func newApplicabilityScanManager(xrayScanResults []services.ScanResponse, directDependencies []string, scanner *AdvancedSecurityScanner) (manager *ApplicabilityScanManager) { directDependenciesCves := extractDirectDependenciesCvesFromScan(xrayScanResults, directDependencies) return &ApplicabilityScanManager{ - applicabilityScanResults: map[string]string{}, + applicabilityScanResults: map[string]utils.ApplicabilityStatus{}, directDependenciesCves: directDependenciesCves, xrayResults: xrayScanResults, scanner: scanner, @@ -63,7 +63,7 @@ func newApplicabilityScanManager(xrayScanResults []services.ScanResponse, direct // This function gets a list of xray scan responses that contain direct and indirect vulnerabilities and returns only direct // vulnerabilities of the scanned project, ignoring indirect vulnerabilities -func extractDirectDependenciesCvesFromScan(xrayScanResults []services.ScanResponse, directDependencies []string) *datastructures.Set[string] { +func extractDirectDependenciesCvesFromScan(xrayScanResults []services.ScanResponse, directDependencies []string) []string { directsCves := datastructures.MakeSet[string]() for _, scanResult := range xrayScanResults { for _, vulnerability := range scanResult.Vulnerabilities { @@ -86,7 +86,7 @@ func extractDirectDependenciesCvesFromScan(xrayScanResults []services.ScanRespon } } - return directsCves + return directsCves.ToSlice() } func isDirectComponents(components []string, directDependencies []string) bool { @@ -110,7 +110,7 @@ func (a *ApplicabilityScanManager) Run(wd string) (err error) { if err = a.runAnalyzerManager(); err != nil { return } - var workingDirResults map[string]string + var workingDirResults map[string]utils.ApplicabilityStatus if workingDirResults, err = a.getScanResults(); err != nil { return } @@ -121,7 +121,7 @@ func (a *ApplicabilityScanManager) Run(wd string) (err error) { } func (a *ApplicabilityScanManager) directDependenciesExist() bool { - return a.directDependenciesCves.Size() > 0 + return len(a.directDependenciesCves) > 0 } func (a *ApplicabilityScanManager) shouldRunApplicabilityScan(technologies []coreutils.Technology) bool { @@ -149,7 +149,7 @@ func (a *ApplicabilityScanManager) createConfigFile(workingDir string) error { Output: a.scanner.resultsFileName, Type: applicabilityScanType, GrepDisable: false, - CveWhitelist: a.directDependenciesCves.ToSlice(), + CveWhitelist: a.directDependenciesCves, SkippedDirs: skippedDirs, }, }, @@ -163,37 +163,36 @@ func (a *ApplicabilityScanManager) runAnalyzerManager() error { return a.scanner.analyzerManager.Exec(a.scanner.configFileName, applicabilityScanCommand, filepath.Dir(a.scanner.analyzerManager.AnalyzerManagerFullPath), a.scanner.serverDetails) } -func (a *ApplicabilityScanManager) getScanResults() (map[string]string, error) { - report, err := sarif.Open(a.scanner.resultsFileName) - if errorutils.CheckError(err) != nil { - return nil, err - } - var fullVulnerabilitiesList []*sarif.Result - if len(report.Runs) > 0 { - fullVulnerabilitiesList = report.Runs[0].Results +func (a *ApplicabilityScanManager) getScanResults() (applicabilityResults map[string]utils.ApplicabilityStatus, err error) { + applicabilityResults = make(map[string]utils.ApplicabilityStatus, len(a.directDependenciesCves)) + for _, cve := range a.directDependenciesCves { + applicabilityResults[cve] = utils.ApplicabilityUndetermined } - applicabilityScanResults := make(map[string]string) - for _, cve := range a.directDependenciesCves.ToSlice() { - applicabilityScanResults[cve] = utils.ApplicabilityUndeterminedStringValue + report, err := sarif.Open(a.scanner.resultsFileName) + if errorutils.CheckError(err) != nil || len(report.Runs) == 0 { + return } - - for _, vulnerability := range fullVulnerabilitiesList { - applicableVulnerabilityName := getVulnerabilityName(*vulnerability.RuleID) - if isVulnerabilityApplicable(vulnerability) { - applicabilityScanResults[applicableVulnerabilityName] = utils.ApplicableStringValue - } else { - applicabilityScanResults[applicableVulnerabilityName] = utils.NotApplicableStringValue + // Applicability results contains one run only + for _, sarifResult := range report.Runs[0].Results { + cve := getCveFromRuleId(*sarifResult.RuleID) + if _, exists := applicabilityResults[cve]; !exists { + err = errorutils.CheckErrorf("received unexpected CVE: '%s' from RuleID: '%s' that does not exists on the requested CVEs list", cve, *sarifResult.RuleID) + return } + applicabilityResults[cve] = resultKindToApplicabilityStatus(sarifResult.Kind) } - return applicabilityScanResults, nil + return } // Gets a result of one CVE from the scanner, and returns true if the CVE is applicable, false otherwise -func isVulnerabilityApplicable(result *sarif.Result) bool { - return !(result.Kind != nil && *result.Kind == "pass") +func resultKindToApplicabilityStatus(kind *string) utils.ApplicabilityStatus { + if !(kind != nil && *kind == "pass") { + return utils.Applicable + } + return utils.NotApplicable } -func getVulnerabilityName(sarifRuleId string) string { +func getCveFromRuleId(sarifRuleId string) string { return strings.TrimPrefix(sarifRuleId, "applic_") } diff --git a/xray/audit/jas/applicabilitymanager_test.go b/xray/audit/jas/applicabilitymanager_test.go index 079da7893..ec96df6f9 100644 --- a/xray/audit/jas/applicabilitymanager_test.go +++ b/xray/audit/jas/applicabilitymanager_test.go @@ -21,7 +21,7 @@ func TestNewApplicabilityScanManager_InputIsValid(t *testing.T) { if assert.NotNil(t, applicabilityManager) { assert.NotEmpty(t, applicabilityManager.scanner.configFileName) assert.NotEmpty(t, applicabilityManager.scanner.resultsFileName) - assert.Equal(t, 5, applicabilityManager.directDependenciesCves.Size()) + assert.Len(t, applicabilityManager.directDependenciesCves, 5) } } @@ -37,7 +37,7 @@ func TestNewApplicabilityScanManager_DependencyTreeDoesntExist(t *testing.T) { assert.Len(t, applicabilityManager.scanner.workingDirs, 1) assert.NotEmpty(t, applicabilityManager.scanner.configFileName) assert.NotEmpty(t, applicabilityManager.scanner.resultsFileName) - assert.Equal(t, applicabilityManager.directDependenciesCves.Size(), 0) + assert.Empty(t, applicabilityManager.directDependenciesCves) } } @@ -73,7 +73,7 @@ func TestNewApplicabilityScanManager_NoDirectDependenciesInScan(t *testing.T) { assert.NotEmpty(t, applicabilityManager.scanner.configFileName) assert.NotEmpty(t, applicabilityManager.scanner.resultsFileName) // Non-direct dependencies should not be added - assert.Equal(t, 0, applicabilityManager.directDependenciesCves.Size()) + assert.Empty(t, applicabilityManager.directDependenciesCves) } } @@ -88,7 +88,7 @@ func TestNewApplicabilityScanManager_MultipleDependencyTrees(t *testing.T) { if assert.NotNil(t, applicabilityManager) { assert.NotEmpty(t, applicabilityManager.scanner.configFileName) assert.NotEmpty(t, applicabilityManager.scanner.resultsFileName) - assert.Equal(t, 5, applicabilityManager.directDependenciesCves.Size()) + assert.Len(t, applicabilityManager.directDependenciesCves, 5) } } @@ -114,7 +114,7 @@ func TestNewApplicabilityScanManager_ViolationsDontExistInResults(t *testing.T) if assert.NotNil(t, applicabilityManager) { assert.NotEmpty(t, applicabilityManager.scanner.configFileName) assert.NotEmpty(t, applicabilityManager.scanner.resultsFileName) - assert.Equal(t, 3, applicabilityManager.directDependenciesCves.Size()) + assert.Len(t, applicabilityManager.directDependenciesCves, 3) } } @@ -140,7 +140,7 @@ func TestNewApplicabilityScanManager_VulnerabilitiesDontExist(t *testing.T) { if assert.NotNil(t, applicabilityManager) { assert.NotEmpty(t, applicabilityManager.scanner.configFileName) assert.NotEmpty(t, applicabilityManager.scanner.resultsFileName) - assert.Equal(t, 2, applicabilityManager.directDependenciesCves.Size()) + assert.Len(t, applicabilityManager.directDependenciesCves, 2) } } @@ -196,7 +196,7 @@ func TestExtractXrayDirectViolations(t *testing.T) { for _, test := range tests { cves := extractDirectDependenciesCvesFromScan(xrayResponseForDirectViolationsTest, test.directDependencies) - assert.Equal(t, test.cvesCount, cves.Size()) + assert.Len(t, cves, test.cvesCount) } } @@ -236,7 +236,7 @@ func TestExtractXrayDirectVulnerabilities(t *testing.T) { } for _, test := range tests { - assert.Equal(t, test.cvesCount, extractDirectDependenciesCvesFromScan(xrayResponseForDirectVulnerabilitiesTest, test.directDependencies).Size()) + assert.Len(t, extractDirectDependenciesCvesFromScan(xrayResponseForDirectVulnerabilitiesTest, test.directDependencies), test.cvesCount) } } @@ -279,7 +279,7 @@ func TestParseResults_EmptyResults_AllCvesShouldGetUnknown(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 5, len(results)) for _, cveResult := range results { - assert.Equal(t, utils.ApplicabilityUndeterminedStringValue, cveResult) + assert.Equal(t, utils.ApplicabilityUndetermined, cveResult) } } @@ -296,8 +296,8 @@ func TestParseResults_ApplicableCveExist(t *testing.T) { // Assert assert.NoError(t, err) assert.Equal(t, 5, len(results)) - assert.Equal(t, utils.ApplicableStringValue, results["testCve1"]) - assert.Equal(t, utils.NotApplicableStringValue, results["testCve3"]) + assert.Equal(t, utils.Applicable, results["testCve1"]) + assert.Equal(t, utils.NotApplicable, results["testCve3"]) } func TestParseResults_AllCvesNotApplicable(t *testing.T) { @@ -314,7 +314,7 @@ func TestParseResults_AllCvesNotApplicable(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 5, len(results)) for _, cveResult := range results { - assert.Equal(t, utils.NotApplicableStringValue, cveResult) + assert.Equal(t, utils.NotApplicable, cveResult) } } diff --git a/xray/commands/audit/generic/auditmanager.go b/xray/commands/audit/audit.go similarity index 70% rename from xray/commands/audit/generic/auditmanager.go rename to xray/commands/audit/audit.go index 7800dc10d..03c309b82 100644 --- a/xray/commands/audit/generic/auditmanager.go +++ b/xray/commands/audit/audit.go @@ -1,13 +1,11 @@ package audit import ( - "encoding/json" "errors" "fmt" "github.com/jfrog/build-info-go/utils/pythonutils" "github.com/jfrog/gofrog/datastructures" rtutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" - "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-cli-core/v2/xray/audit" _go "github.com/jfrog/jfrog-cli-core/v2/xray/audit/go" "github.com/jfrog/jfrog-cli-core/v2/xray/audit/jas" @@ -17,87 +15,140 @@ import ( "github.com/jfrog/jfrog-cli-core/v2/xray/audit/python" "github.com/jfrog/jfrog-cli-core/v2/xray/audit/yarn" commandsutils "github.com/jfrog/jfrog-cli-core/v2/xray/commands/utils" - xrayutils "github.com/jfrog/jfrog-cli-core/v2/xray/utils" clientutils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/log" "github.com/jfrog/jfrog-client-go/xray" - "github.com/jfrog/jfrog-client-go/xray/services" xrayCmdUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" "golang.org/x/sync/errgroup" "os" "time" -) -type Params struct { - xrayGraphScanParams *services.XrayGraphScanParams - workingDirs []string - installFunc func(tech string) error - fixableOnly bool - minSeverityFilter string - *xrayutils.GraphBasicParams - xrayVersion string -} + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-client-go/xray/services" -func NewAuditParams() *Params { - return &Params{ - xrayGraphScanParams: &services.XrayGraphScanParams{}, - GraphBasicParams: &xrayutils.GraphBasicParams{}, - } -} + "encoding/json" + xrayutils "github.com/jfrog/jfrog-cli-core/v2/xray/utils" +) -func (params *Params) InstallFunc() func(tech string) error { - return params.installFunc +type AuditCommand struct { + watches []string + projectKey string + targetRepoPath string + IncludeVulnerabilities bool + IncludeLicenses bool + Fail bool + PrintExtendedTable bool + AuditParams } -func (params *Params) XrayGraphScanParams() *services.XrayGraphScanParams { - return params.xrayGraphScanParams +func NewGenericAuditCommand() *AuditCommand { + return &AuditCommand{AuditParams: *NewAuditParams()} } -func (params *Params) WorkingDirs() []string { - return params.workingDirs +func (auditCmd *AuditCommand) SetWatches(watches []string) *AuditCommand { + auditCmd.watches = watches + return auditCmd } -func (params *Params) XrayVersion() string { - return params.xrayVersion +func (auditCmd *AuditCommand) SetProject(project string) *AuditCommand { + auditCmd.projectKey = project + return auditCmd } -func (params *Params) SetXrayGraphScanParams(xrayGraphScanParams *services.XrayGraphScanParams) *Params { - params.xrayGraphScanParams = xrayGraphScanParams - return params +func (auditCmd *AuditCommand) SetTargetRepoPath(repoPath string) *AuditCommand { + auditCmd.targetRepoPath = repoPath + return auditCmd } -func (params *Params) SetGraphBasicParams(gbp *xrayutils.GraphBasicParams) *Params { - params.GraphBasicParams = gbp - return params +func (auditCmd *AuditCommand) SetIncludeVulnerabilities(include bool) *AuditCommand { + auditCmd.IncludeVulnerabilities = include + return auditCmd } -func (params *Params) SetWorkingDirs(workingDirs []string) *Params { - params.workingDirs = workingDirs - return params +func (auditCmd *AuditCommand) SetIncludeLicenses(include bool) *AuditCommand { + auditCmd.IncludeLicenses = include + return auditCmd } -func (params *Params) SetInstallFunc(installFunc func(tech string) error) *Params { - params.installFunc = installFunc - return params +func (auditCmd *AuditCommand) SetFail(fail bool) *AuditCommand { + auditCmd.Fail = fail + return auditCmd } -func (params *Params) FixableOnly() bool { - return params.fixableOnly +func (auditCmd *AuditCommand) SetPrintExtendedTable(printExtendedTable bool) *AuditCommand { + auditCmd.PrintExtendedTable = printExtendedTable + return auditCmd } -func (params *Params) SetFixableOnly(fixable bool) *Params { - params.fixableOnly = fixable +func (auditCmd *AuditCommand) CreateXrayGraphScanParams() *services.XrayGraphScanParams { + params := &services.XrayGraphScanParams{ + RepoPath: auditCmd.targetRepoPath, + Watches: auditCmd.watches, + ScanType: services.Dependency, + } + if auditCmd.projectKey == "" { + params.ProjectKey = os.Getenv(coreutils.Project) + } else { + params.ProjectKey = auditCmd.projectKey + } + params.IncludeVulnerabilities = auditCmd.IncludeVulnerabilities + params.IncludeLicenses = auditCmd.IncludeLicenses return params } -func (params *Params) MinSeverityFilter() string { - return params.minSeverityFilter +func (auditCmd *AuditCommand) Run() (err error) { + workingDirs, err := xrayutils.GetFullPathsWorkingDirs(auditCmd.workingDirs) + if err != nil { + return + } + auditParams := NewAuditParams(). + SetXrayGraphScanParams(auditCmd.CreateXrayGraphScanParams()). + SetWorkingDirs(workingDirs). + SetMinSeverityFilter(auditCmd.minSeverityFilter). + SetFixableOnly(auditCmd.fixableOnly). + SetGraphBasicParams(auditCmd.GraphBasicParams) + auditResults, err := RunAudit(auditParams) + if err != nil { + return + } + if auditCmd.Progress() != nil { + if err = auditCmd.Progress().Quit(); err != nil { + return + } + } + var messages []string + if !auditResults.ExtendedScanResults.EntitledForJas { + messages = []string{coreutils.PrintTitle("The ‘jf audit’ command also supports JFrog Advanced Security features, such as 'Contextual Analysis', 'Secret Detection', 'IaC Scan' and ‘SAST’.\nThis feature isn't enabled on your system. Read more - ") + coreutils.PrintLink("https://jfrog.com/xray/")} + } + // Print Scan results on all cases except if errors accrued on SCA scan and no security/license issues found. + printScanResults := !(auditResults.ScaError != nil && xrayutils.IsEmptyScanResponse(auditResults.ExtendedScanResults.XrayResults)) + if printScanResults { + err = xrayutils.PrintScanResults(auditResults.ExtendedScanResults, + nil, + auditCmd.OutputFormat(), + auditCmd.IncludeVulnerabilities, + auditCmd.IncludeLicenses, + auditResults.IsMultipleRootProject, + auditCmd.PrintExtendedTable, false, messages, + ) + if err != nil { + return + } + } + if err = errors.Join(auditResults.ScaError, auditResults.JasError); err != nil { + return + } + + // Only in case Xray's context was given (!auditCmd.IncludeVulnerabilities), and the user asked to fail the build accordingly, do so. + if auditCmd.Fail && !auditCmd.IncludeVulnerabilities && xrayutils.CheckIfFailBuild(auditResults.ExtendedScanResults.XrayResults) { + err = xrayutils.NewFailBuildError() + } + return } -func (params *Params) SetMinSeverityFilter(minSeverityFilter string) *Params { - params.minSeverityFilter = minSeverityFilter - return params +func (auditCmd *AuditCommand) CommandName() string { + return "generic_audit" } type Results struct { @@ -114,7 +165,7 @@ func NewAuditResults() *Results { // Runs an audit scan based on the provided auditParams. // Returns an audit Results object containing all the scan results. // If the current server is entitled for JAS, the advanced security results will be included in the scan results. -func RunAudit(auditParams *Params) (results *Results, err error) { +func RunAudit(auditParams *AuditParams) (results *Results, err error) { // Initialize Results struct results = NewAuditResults() @@ -165,7 +216,7 @@ func isEntitledForJas(xrayManager *xray.XrayServicesManager, xrayVersion string) return } -func runScaScan(params *Params, results *Results) (err error) { +func runScaScan(params *AuditParams, results *Results) (err error) { rootDir, err := os.Getwd() if errorutils.CheckError(err) != nil { return @@ -186,7 +237,7 @@ func runScaScan(params *Params, results *Results) (err error) { } // Audits the project found in the current directory using Xray. -func runScaScanOnWorkingDir(params *Params, results *Results, workingDir, rootDir string) (err error) { +func runScaScanOnWorkingDir(params *AuditParams, results *Results, workingDir, rootDir string) (err error) { err = os.Chdir(workingDir) if err != nil { return diff --git a/xray/commands/audit/generic/auditmanager_test.go b/xray/commands/audit/audit_test.go similarity index 100% rename from xray/commands/audit/generic/auditmanager_test.go rename to xray/commands/audit/audit_test.go diff --git a/xray/commands/audit/auditparams.go b/xray/commands/audit/auditparams.go new file mode 100644 index 000000000..b7a802257 --- /dev/null +++ b/xray/commands/audit/auditparams.go @@ -0,0 +1,77 @@ +package audit + +import ( + xrayutils "github.com/jfrog/jfrog-cli-core/v2/xray/utils" + "github.com/jfrog/jfrog-client-go/xray/services" +) + +type AuditParams struct { + xrayGraphScanParams *services.XrayGraphScanParams + workingDirs []string + installFunc func(tech string) error + fixableOnly bool + minSeverityFilter string + *xrayutils.GraphBasicParams + xrayVersion string +} + +func NewAuditParams() *AuditParams { + return &AuditParams{ + xrayGraphScanParams: &services.XrayGraphScanParams{}, + GraphBasicParams: &xrayutils.GraphBasicParams{}, + } +} + +func (params *AuditParams) InstallFunc() func(tech string) error { + return params.installFunc +} + +func (params *AuditParams) XrayGraphScanParams() *services.XrayGraphScanParams { + return params.xrayGraphScanParams +} + +func (params *AuditParams) WorkingDirs() []string { + return params.workingDirs +} + +func (params *AuditParams) XrayVersion() string { + return params.xrayVersion +} + +func (params *AuditParams) SetXrayGraphScanParams(xrayGraphScanParams *services.XrayGraphScanParams) *AuditParams { + params.xrayGraphScanParams = xrayGraphScanParams + return params +} + +func (params *AuditParams) SetGraphBasicParams(gbp *xrayutils.GraphBasicParams) *AuditParams { + params.GraphBasicParams = gbp + return params +} + +func (params *AuditParams) SetWorkingDirs(workingDirs []string) *AuditParams { + params.workingDirs = workingDirs + return params +} + +func (params *AuditParams) SetInstallFunc(installFunc func(tech string) error) *AuditParams { + params.installFunc = installFunc + return params +} + +func (params *AuditParams) FixableOnly() bool { + return params.fixableOnly +} + +func (params *AuditParams) SetFixableOnly(fixable bool) *AuditParams { + params.fixableOnly = fixable + return params +} + +func (params *AuditParams) MinSeverityFilter() string { + return params.minSeverityFilter +} + +func (params *AuditParams) SetMinSeverityFilter(minSeverityFilter string) *AuditParams { + params.minSeverityFilter = minSeverityFilter + return params +} diff --git a/xray/commands/audit/generic/generic.go b/xray/commands/audit/generic/generic.go deleted file mode 100644 index 939d78db4..000000000 --- a/xray/commands/audit/generic/generic.go +++ /dev/null @@ -1,130 +0,0 @@ -package audit - -import ( - "errors" - "os" - - "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - xrutils "github.com/jfrog/jfrog-cli-core/v2/xray/utils" - "github.com/jfrog/jfrog-client-go/xray/services" -) - -type GenericAuditCommand struct { - watches []string - projectKey string - targetRepoPath string - IncludeVulnerabilities bool - IncludeLicenses bool - Fail bool - PrintExtendedTable bool - Params -} - -func NewGenericAuditCommand() *GenericAuditCommand { - return &GenericAuditCommand{Params: *NewAuditParams()} -} - -func (auditCmd *GenericAuditCommand) SetWatches(watches []string) *GenericAuditCommand { - auditCmd.watches = watches - return auditCmd -} - -func (auditCmd *GenericAuditCommand) SetProject(project string) *GenericAuditCommand { - auditCmd.projectKey = project - return auditCmd -} - -func (auditCmd *GenericAuditCommand) SetTargetRepoPath(repoPath string) *GenericAuditCommand { - auditCmd.targetRepoPath = repoPath - return auditCmd -} - -func (auditCmd *GenericAuditCommand) SetIncludeVulnerabilities(include bool) *GenericAuditCommand { - auditCmd.IncludeVulnerabilities = include - return auditCmd -} - -func (auditCmd *GenericAuditCommand) SetIncludeLicenses(include bool) *GenericAuditCommand { - auditCmd.IncludeLicenses = include - return auditCmd -} - -func (auditCmd *GenericAuditCommand) SetFail(fail bool) *GenericAuditCommand { - auditCmd.Fail = fail - return auditCmd -} - -func (auditCmd *GenericAuditCommand) SetPrintExtendedTable(printExtendedTable bool) *GenericAuditCommand { - auditCmd.PrintExtendedTable = printExtendedTable - return auditCmd -} - -func (auditCmd *GenericAuditCommand) CreateXrayGraphScanParams() *services.XrayGraphScanParams { - params := &services.XrayGraphScanParams{ - RepoPath: auditCmd.targetRepoPath, - Watches: auditCmd.watches, - ScanType: services.Dependency, - } - if auditCmd.projectKey == "" { - params.ProjectKey = os.Getenv(coreutils.Project) - } else { - params.ProjectKey = auditCmd.projectKey - } - params.IncludeVulnerabilities = auditCmd.IncludeVulnerabilities - params.IncludeLicenses = auditCmd.IncludeLicenses - return params -} - -func (auditCmd *GenericAuditCommand) Run() (err error) { - workingDirs, err := xrutils.GetFullPathsWorkingDirs(auditCmd.workingDirs) - if err != nil { - return - } - auditParams := NewAuditParams(). - SetXrayGraphScanParams(auditCmd.CreateXrayGraphScanParams()). - SetWorkingDirs(workingDirs). - SetMinSeverityFilter(auditCmd.minSeverityFilter). - SetFixableOnly(auditCmd.fixableOnly). - SetGraphBasicParams(auditCmd.GraphBasicParams) - auditResults, err := RunAudit(auditParams) - if err != nil { - return - } - if auditCmd.Progress() != nil { - if err = auditCmd.Progress().Quit(); err != nil { - return - } - } - var messages []string - if !auditResults.ExtendedScanResults.EntitledForJas { - messages = []string{coreutils.PrintTitle("The ‘jf audit’ command also supports JFrog Advanced Security features, such as 'Contextual Analysis', 'Secret Detection', 'IaC Scan' and ‘SAST’.\nThis feature isn't enabled on your system. Read more - ") + coreutils.PrintLink("https://jfrog.com/xray/")} - } - // Print Scan results on all cases except if errors accrued on SCA scan and no security/license issues found. - printScanResults := !(auditResults.ScaError != nil && xrutils.IsEmptyScanResponse(auditResults.ExtendedScanResults.XrayResults)) - if printScanResults { - err = xrutils.PrintScanResults(auditResults.ExtendedScanResults, - nil, - auditCmd.OutputFormat(), - auditCmd.IncludeVulnerabilities, - auditCmd.IncludeLicenses, - auditResults.IsMultipleRootProject, - auditCmd.PrintExtendedTable, false, messages, - ) - if err != nil { - return - } - } - if err = errors.Join(auditResults.ScaError, auditResults.JasError); err != nil { - return - } - - // Only in case Xray's context was given (!auditCmd.IncludeVulnerabilities), and the user asked to fail the build accordingly, do so. - if auditCmd.Fail && !auditCmd.IncludeVulnerabilities && xrutils.CheckIfFailBuild(auditResults.ExtendedScanResults.XrayResults) { - err = xrutils.NewFailBuildError() - } - return -} - -func (auditCmd *GenericAuditCommand) CommandName() string { - return "generic_audit" -} diff --git a/xray/commands/curation/audit.go b/xray/commands/curation/audit.go index 5822f22e7..47d651f9d 100644 --- a/xray/commands/curation/audit.go +++ b/xray/commands/curation/audit.go @@ -8,7 +8,7 @@ import ( "github.com/jfrog/gofrog/parallel" rtUtils "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - audit "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/generic" + "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit" cmdUtils "github.com/jfrog/jfrog-cli-core/v2/xray/commands/utils" "github.com/jfrog/jfrog-cli-core/v2/xray/utils" "github.com/jfrog/jfrog-client-go/artifactory" diff --git a/xray/commands/utils/utils.go b/xray/commands/utils/utils.go index b3a4ccc2d..e825b7349 100644 --- a/xray/commands/utils/utils.go +++ b/xray/commands/utils/utils.go @@ -25,7 +25,7 @@ const ( ) func getLevelOfSeverity(s string) int { - severity := utils.GetSeverity(cases.Title(language.Und).String(s), utils.ApplicabilityUndeterminedStringValue) + severity := utils.GetSeverity(cases.Title(language.Und).String(s), utils.ApplicabilityUndetermined) return severity.NumValue() } diff --git a/xray/utils/analyzermanager.go b/xray/utils/analyzermanager.go index 48fa16c36..f611da5d1 100644 --- a/xray/utils/analyzermanager.go +++ b/xray/utils/analyzermanager.go @@ -58,10 +58,13 @@ const ( ErrFailedScannerRun = "failed to run %s scan. Exit code received: %s" ) +type ApplicabilityStatus string + const ( - ApplicableStringValue = "Applicable" - NotApplicableStringValue = "Not Applicable" - ApplicabilityUndeterminedStringValue = "Undetermined" + Applicable ApplicabilityStatus = "Applicable" + NotApplicable ApplicabilityStatus = "Not Applicable" + ApplicabilityUndetermined ApplicabilityStatus = "Undetermined" + NotScanned ApplicabilityStatus = "" ) type JasScanType string @@ -102,7 +105,7 @@ type SourceCodeScanResult struct { type ExtendedScanResults struct { XrayResults []services.ScanResponse ScannedTechnologies []coreutils.Technology - ApplicabilityScanResults map[string]string + ApplicabilityScanResults map[string]ApplicabilityStatus SecretsScanResults []SourceCodeScanResult IacScanResults []SourceCodeScanResult SastResults []SourceCodeScanResult diff --git a/xray/utils/resultstable.go b/xray/utils/resultstable.go index 917109b4a..3f5734a14 100644 --- a/xray/utils/resultstable.go +++ b/xray/utils/resultstable.go @@ -112,7 +112,7 @@ func prepareViolations(violations []services.Violation, extendedResults *Extende ) } case "license": - currSeverity := GetSeverity(violation.Severity, ApplicabilityUndeterminedStringValue) + currSeverity := GetSeverity(violation.Severity, ApplicabilityUndetermined) for compIndex := 0; compIndex < len(impactedPackagesNames); compIndex++ { licenseViolationsRows = append(licenseViolationsRows, formats.LicenseViolationRow{ @@ -127,7 +127,7 @@ func prepareViolations(violations []services.Violation, extendedResults *Extende ) } case "operational_risk": - currSeverity := GetSeverity(violation.Severity, ApplicabilityUndeterminedStringValue) + currSeverity := GetSeverity(violation.Severity, ApplicabilityUndetermined) violationOpRiskData := getOperationalRiskViolationReadableData(violation) for compIndex := 0; compIndex < len(impactedPackagesNames); compIndex++ { operationalRiskViolationsRow := &formats.OperationalRiskViolationRow{ @@ -291,7 +291,7 @@ func PrepareSecrets(secrets []SourceCodeScanResult) []formats.SourceCodeRow { func prepareSecrets(secrets []SourceCodeScanResult, isTable bool) []formats.SourceCodeRow { var secretsRows []formats.SourceCodeRow for _, secret := range secrets { - currSeverity := GetSeverity(secret.Severity, ApplicableStringValue) + currSeverity := GetSeverity(secret.Severity, Applicable) secretsRows = append(secretsRows, formats.SourceCodeRow{ Severity: currSeverity.printableTitle(isTable), @@ -331,7 +331,7 @@ func PrepareIacs(iacs []SourceCodeScanResult) []formats.SourceCodeRow { func prepareIacs(iacs []SourceCodeScanResult, isTable bool) []formats.SourceCodeRow { var iacRows []formats.SourceCodeRow for _, iac := range iacs { - currSeverity := GetSeverity(iac.Severity, ApplicableStringValue) + currSeverity := GetSeverity(iac.Severity, Applicable) iacRows = append(iacRows, formats.SourceCodeRow{ Severity: currSeverity.printableTitle(isTable), @@ -370,7 +370,7 @@ func PrepareSast(sasts []SourceCodeScanResult) []formats.SourceCodeRow { func prepareSast(sasts []SourceCodeScanResult, isTable bool) []formats.SourceCodeRow { var sastRows []formats.SourceCodeRow for _, sast := range sasts { - currSeverity := GetSeverity(sast.Severity, ApplicableStringValue) + currSeverity := GetSeverity(sast.Severity, Applicable) sastRows = append(sastRows, formats.SourceCodeRow{ Severity: currSeverity.printableTitle(isTable), @@ -598,31 +598,31 @@ func (s *Severity) printableTitle(isTable bool) string { return s.title } -var Severities = map[string]map[string]*Severity{ +var Severities = map[string]map[ApplicabilityStatus]*Severity{ "Critical": { - ApplicableStringValue: {emoji: "💀", title: "Critical", numValue: 15, style: color.New(color.BgLightRed, color.LightWhite)}, - ApplicabilityUndeterminedStringValue: {emoji: "💀", title: "Critical", numValue: 14, style: color.New(color.BgLightRed, color.LightWhite)}, - NotApplicableStringValue: {emoji: "💀", title: "Critical", numValue: 5, style: color.New(color.Gray)}, + Applicable: {emoji: "💀", title: "Critical", numValue: 15, style: color.New(color.BgLightRed, color.LightWhite)}, + ApplicabilityUndetermined: {emoji: "💀", title: "Critical", numValue: 14, style: color.New(color.BgLightRed, color.LightWhite)}, + NotApplicable: {emoji: "💀", title: "Critical", numValue: 5, style: color.New(color.Gray)}, }, "High": { - ApplicableStringValue: {emoji: "🔥", title: "High", numValue: 13, style: color.New(color.Red)}, - ApplicabilityUndeterminedStringValue: {emoji: "🔥", title: "High", numValue: 12, style: color.New(color.Red)}, - NotApplicableStringValue: {emoji: "🔥", title: "High", numValue: 4, style: color.New(color.Gray)}, + Applicable: {emoji: "🔥", title: "High", numValue: 13, style: color.New(color.Red)}, + ApplicabilityUndetermined: {emoji: "🔥", title: "High", numValue: 12, style: color.New(color.Red)}, + NotApplicable: {emoji: "🔥", title: "High", numValue: 4, style: color.New(color.Gray)}, }, "Medium": { - ApplicableStringValue: {emoji: "🎃", title: "Medium", numValue: 11, style: color.New(color.Yellow)}, - ApplicabilityUndeterminedStringValue: {emoji: "🎃", title: "Medium", numValue: 10, style: color.New(color.Yellow)}, - NotApplicableStringValue: {emoji: "🎃", title: "Medium", numValue: 3, style: color.New(color.Gray)}, + Applicable: {emoji: "🎃", title: "Medium", numValue: 11, style: color.New(color.Yellow)}, + ApplicabilityUndetermined: {emoji: "🎃", title: "Medium", numValue: 10, style: color.New(color.Yellow)}, + NotApplicable: {emoji: "🎃", title: "Medium", numValue: 3, style: color.New(color.Gray)}, }, "Low": { - ApplicableStringValue: {emoji: "👻", title: "Low", numValue: 9}, - ApplicabilityUndeterminedStringValue: {emoji: "👻", title: "Low", numValue: 8}, - NotApplicableStringValue: {emoji: "👻", title: "Low", numValue: 2, style: color.New(color.Gray)}, + Applicable: {emoji: "👻", title: "Low", numValue: 9}, + ApplicabilityUndetermined: {emoji: "👻", title: "Low", numValue: 8}, + NotApplicable: {emoji: "👻", title: "Low", numValue: 2, style: color.New(color.Gray)}, }, "Unknown": { - ApplicableStringValue: {emoji: "😐", title: "Unknown", numValue: 7}, - ApplicabilityUndeterminedStringValue: {emoji: "😐", title: "Unknown", numValue: 6}, - NotApplicableStringValue: {emoji: "😐", title: "Unknown", numValue: 1, style: color.New(color.Gray)}, + Applicable: {emoji: "😐", title: "Unknown", numValue: 7}, + ApplicabilityUndetermined: {emoji: "😐", title: "Unknown", numValue: 6}, + NotApplicable: {emoji: "😐", title: "Unknown", numValue: 1, style: color.New(color.Gray)}, }, } @@ -636,25 +636,25 @@ func (s *Severity) Emoji() string { func GetSeveritiesFormat(severity string) (string, error) { formattedSeverity := cases.Title(language.Und).String(severity) - if formattedSeverity != "" && Severities[formattedSeverity][ApplicableStringValue] == nil { + if formattedSeverity != "" && Severities[formattedSeverity][Applicable] == nil { return "", errorutils.CheckErrorf("only the following severities are supported: " + coreutils.ListToText(maps.Keys(Severities))) } return formattedSeverity, nil } -func GetSeverity(severityTitle string, applicable string) *Severity { +func GetSeverity(severityTitle string, applicable ApplicabilityStatus) *Severity { if Severities[severityTitle] == nil { return &Severity{title: severityTitle} } switch applicable { - case NotApplicableStringValue: - return Severities[severityTitle][NotApplicableStringValue] - case ApplicableStringValue: - return Severities[severityTitle][ApplicableStringValue] + case NotApplicable: + return Severities[severityTitle][NotApplicable] + case Applicable: + return Severities[severityTitle][Applicable] default: - return Severities[severityTitle][ApplicabilityUndeterminedStringValue] + return Severities[severityTitle][ApplicabilityUndetermined] } } @@ -876,21 +876,22 @@ func GetUniqueKey(vulnerableDependency, vulnerableVersion, xrayID string, fixVer // If at least one cve is applicable - final value is applicable // Else if at least one cve is undetermined - final value is undetermined // Else (case when all cves aren't applicable) -> final value is not applicable -func getApplicableCveValue(extendedResults *ExtendedScanResults, xrayCves []formats.CveRow) string { +func getApplicableCveValue(extendedResults *ExtendedScanResults, xrayCves []formats.CveRow) ApplicabilityStatus { if !extendedResults.EntitledForJas || len(extendedResults.ApplicabilityScanResults) == 0 { - return "" + return NotScanned } + if len(xrayCves) == 0 { - return ApplicabilityUndeterminedStringValue + return ApplicabilityUndetermined } cveExistsInResult := false - finalApplicableValue := NotApplicableStringValue + finalApplicableValue := NotApplicable for _, cve := range xrayCves { if currentCveApplicableValue, exists := extendedResults.ApplicabilityScanResults[cve.Id]; exists { cveExistsInResult = true - if currentCveApplicableValue == ApplicableStringValue { + if currentCveApplicableValue == Applicable { return currentCveApplicableValue - } else if currentCveApplicableValue == ApplicabilityUndeterminedStringValue { + } else if currentCveApplicableValue == ApplicabilityUndetermined { finalApplicableValue = currentCveApplicableValue } } @@ -898,16 +899,16 @@ func getApplicableCveValue(extendedResults *ExtendedScanResults, xrayCves []form if cveExistsInResult { return finalApplicableValue } - return ApplicabilityUndeterminedStringValue + return ApplicabilityUndetermined } -func printApplicableCveValue(applicableValue string, isTable bool) string { +func printApplicableCveValue(applicableValue ApplicabilityStatus, isTable bool) string { if isTable && (log.IsStdOutTerminal() && log.IsColorsSupported() || os.Getenv("GITLAB_CI") != "") { - if applicableValue == ApplicableStringValue { + if applicableValue == Applicable { return color.New(color.Red).Render(applicableValue) - } else if applicableValue == NotApplicableStringValue { + } else if applicableValue == NotApplicable { return color.New(color.Green).Render(applicableValue) } } - return applicableValue + return string(applicableValue) } diff --git a/xray/utils/resultstable_test.go b/xray/utils/resultstable_test.go index 0f102862d..e7f8b4848 100644 --- a/xray/utils/resultstable_test.go +++ b/xray/utils/resultstable_test.go @@ -427,33 +427,58 @@ func TestGetApplicableCveValue(t *testing.T) { testCases := []struct { scanResults *ExtendedScanResults cves []formats.CveRow - expectedResult string + expectedResult ApplicabilityStatus }{ - {scanResults: &ExtendedScanResults{EntitledForJas: false}, expectedResult: ""}, - {scanResults: &ExtendedScanResults{ - ApplicabilityScanResults: map[string]string{"testCve1": ApplicableStringValue, "testCve2": NotApplicableStringValue}, - EntitledForJas: true}, - cves: nil, expectedResult: ApplicabilityUndeterminedStringValue}, - {scanResults: &ExtendedScanResults{ - ApplicabilityScanResults: map[string]string{"testCve1": NotApplicableStringValue, "testCve2": ApplicableStringValue}, - EntitledForJas: true}, - cves: []formats.CveRow{{Id: "testCve2"}}, expectedResult: ApplicableStringValue}, - {scanResults: &ExtendedScanResults{ - ApplicabilityScanResults: map[string]string{"testCve1": NotApplicableStringValue, "testCve2": ApplicableStringValue}, - EntitledForJas: true}, - cves: []formats.CveRow{{Id: "testCve3"}}, expectedResult: ApplicabilityUndeterminedStringValue}, - {scanResults: &ExtendedScanResults{ - ApplicabilityScanResults: map[string]string{"testCve1": NotApplicableStringValue, "testCve2": NotApplicableStringValue}, - EntitledForJas: true}, - cves: []formats.CveRow{{Id: "testCve1"}, {Id: "testCve2"}}, expectedResult: NotApplicableStringValue}, - {scanResults: &ExtendedScanResults{ - ApplicabilityScanResults: map[string]string{"testCve1": NotApplicableStringValue, "testCve2": ApplicableStringValue}, - EntitledForJas: true}, - cves: []formats.CveRow{{Id: "testCve1"}, {Id: "testCve2"}}, expectedResult: ApplicableStringValue}, - {scanResults: &ExtendedScanResults{ - ApplicabilityScanResults: map[string]string{"testCve1": NotApplicableStringValue, "testCve2": ApplicabilityUndeterminedStringValue}, - EntitledForJas: true}, - cves: []formats.CveRow{{Id: "testCve1"}, {Id: "testCve2"}}, expectedResult: ApplicabilityUndeterminedStringValue}, + { + scanResults: &ExtendedScanResults{EntitledForJas: false}, + expectedResult: NotScanned, + }, + { + scanResults: &ExtendedScanResults{ + ApplicabilityScanResults: map[string]ApplicabilityStatus{"testCve1": Applicable, "testCve2": NotApplicable}, + EntitledForJas: true, + }, + cves: nil, + expectedResult: ApplicabilityUndetermined, + }, + { + scanResults: &ExtendedScanResults{ + ApplicabilityScanResults: map[string]ApplicabilityStatus{"testCve1": NotApplicable, "testCve2": Applicable}, + EntitledForJas: true, + }, + cves: []formats.CveRow{{Id: "testCve2"}}, + expectedResult: Applicable, + }, + { + scanResults: &ExtendedScanResults{ + ApplicabilityScanResults: map[string]ApplicabilityStatus{"testCve1": NotApplicable, "testCve2": Applicable}, + EntitledForJas: true, + }, + cves: []formats.CveRow{{Id: "testCve3"}}, + expectedResult: ApplicabilityUndetermined, + }, + { + scanResults: &ExtendedScanResults{ + ApplicabilityScanResults: map[string]ApplicabilityStatus{"testCve1": NotApplicable, "testCve2": NotApplicable}, + EntitledForJas: true}, + cves: []formats.CveRow{{Id: "testCve1"}, {Id: "testCve2"}}, + expectedResult: NotApplicable, + }, + { + scanResults: &ExtendedScanResults{ + ApplicabilityScanResults: map[string]ApplicabilityStatus{"testCve1": NotApplicable, "testCve2": Applicable}, + EntitledForJas: true, + }, + cves: []formats.CveRow{{Id: "testCve1"}, {Id: "testCve2"}}, + expectedResult: Applicable, + }, + { + scanResults: &ExtendedScanResults{ + ApplicabilityScanResults: map[string]ApplicabilityStatus{"testCve1": NotApplicable, "testCve2": ApplicabilityUndetermined}, + EntitledForJas: true}, + cves: []formats.CveRow{{Id: "testCve1"}, {Id: "testCve2"}}, + expectedResult: ApplicabilityUndetermined, + }, } for _, testCase := range testCases { @@ -525,7 +550,7 @@ func TestSortVulnerabilityOrViolationRows(t *testing.T) { { Summary: "Summary 1", Severity: "Critical", - Applicable: ApplicableStringValue, + Applicable: string(Applicable), SeverityNumValue: 13, FixedVersions: []string{"1.0.0"}, ImpactedDependencyName: "Dependency 1", @@ -533,7 +558,7 @@ func TestSortVulnerabilityOrViolationRows(t *testing.T) { }, { Summary: "Summary 2", - Applicable: NotApplicableStringValue, + Applicable: string(NotApplicable), Severity: "Critical", SeverityNumValue: 11, ImpactedDependencyName: "Dependency 2", @@ -541,7 +566,7 @@ func TestSortVulnerabilityOrViolationRows(t *testing.T) { }, { Summary: "Summary 3", - Applicable: ApplicabilityUndeterminedStringValue, + Applicable: string(ApplicabilityUndetermined), Severity: "Critical", SeverityNumValue: 12, ImpactedDependencyName: "Dependency 3", diff --git a/xray/utils/resultwriter.go b/xray/utils/resultwriter.go index 768229304..a1668e49f 100644 --- a/xray/utils/resultwriter.go +++ b/xray/utils/resultwriter.go @@ -381,7 +381,7 @@ func getSarifTableDescription(formattedDirectDependencies, maxCveScore, applicab if len(fixedVersions) > 0 { descriptionFixVersions = strings.Join(fixedVersions, ", ") } - if applicable == "" { + if applicable == string(NotScanned) { return fmt.Sprintf("| Severity Score | Direct Dependencies | Fixed Versions |\n| :---: | :----: | :---: |\n| %s | %s | %s |", maxCveScore, formattedDirectDependencies, descriptionFixVersions) } diff --git a/xray/utils/resultwriter_test.go b/xray/utils/resultwriter_test.go index 8a015267c..3191f48ef 100644 --- a/xray/utils/resultwriter_test.go +++ b/xray/utils/resultwriter_test.go @@ -192,7 +192,7 @@ func TestGetViolatedDepsSarifProps(t *testing.T) { vulnerability: formats.VulnerabilityOrViolationRow{ Summary: "Vulnerable dependency", Severity: "high", - Applicable: "Applicable", + Applicable: string(Applicable), ImpactedDependencyName: "example-package", ImpactedDependencyVersion: "1.0.0", ImpactedDependencyType: "npm", @@ -221,7 +221,7 @@ func TestGetViolatedDepsSarifProps(t *testing.T) { vulnerability: formats.VulnerabilityOrViolationRow{ Summary: "Vulnerable dependency", Severity: "high", - Applicable: "Applicable", + Applicable: string(Applicable), ImpactedDependencyName: "example-package", ImpactedDependencyVersion: "1.0.0", ImpactedDependencyType: "npm",