diff --git a/cli/utils.go b/cli/utils.go index 2da0e1434..4e362c935 100644 --- a/cli/utils.go +++ b/cli/utils.go @@ -18,6 +18,7 @@ import ( "github.com/jfrog/jfrog-cli-security/sca/bom" "github.com/jfrog/jfrog-cli-security/sca/bom/buildinfo" "github.com/jfrog/jfrog-cli-security/sca/scan" + "github.com/jfrog/jfrog-cli-security/sca/scan/enrich" "github.com/jfrog/jfrog-cli-security/sca/scan/scangraph" flags "github.com/jfrog/jfrog-cli-security/cli/docs" @@ -106,6 +107,11 @@ func splitByCommaAndTrim(paramValue string) (res []string) { return } -func getScanDynamicLogic(_ *components.Context) (bom.SbomGenerator, scan.SbomScanStrategy) { - return buildinfo.NewBuildInfoBomGenerator(), scangraph.NewScanGraphStrategy() +func getScanDynamicLogic(c *components.Context) (bom.SbomGenerator, scan.SbomScanStrategy) { + var bomGenerator bom.SbomGenerator = buildinfo.NewBuildInfoBomGenerator() + var scanStrategy scan.SbomScanStrategy = scangraph.NewScanGraphStrategy() + if c.GetBoolFlagValue("new-sca") { + scanStrategy = enrich.NewEnrichScanStrategy() + } + return bomGenerator, scanStrategy } diff --git a/commands/audit/audit.go b/commands/audit/audit.go index 05eb71e96..25cffcabb 100644 --- a/commands/audit/audit.go +++ b/commands/audit/audit.go @@ -22,6 +22,7 @@ import ( "github.com/jfrog/jfrog-cli-security/utils/results/output" "github.com/jfrog/jfrog-cli-security/utils/techutils" + "github.com/jfrog/jfrog-cli-security/sca/scan/enrich" scanGraphStrategy "github.com/jfrog/jfrog-cli-security/sca/scan/scangraph" "github.com/jfrog/jfrog-cli-security/utils/xsc" "golang.org/x/exp/slices" @@ -261,26 +262,50 @@ func prepareToScan(params *AuditParams) (cmdResults *results.SecurityCommandResu if cmdResults = initAuditCmdResults(params); cmdResults.GeneralError != nil { return } - // Initialize the BOM generator - buildParams, err := params.ToBuildInfoBomGenParams() + bomGenOptions, scanOptions, err := getScanLogicOptions(params) if err != nil { - return results.NewCommandResults(utils.SourceCode).AddGeneralError(fmt.Errorf("failed to create build info params: %s", err.Error()), false) + return cmdResults.AddGeneralError(fmt.Errorf("failed to get scan logic options: %s", err.Error()), false) } - if err = params.bomGenerator.WithOptions(buildinfo.WithParams(buildParams)).PrepareGenerator(); err != nil { + // Initialize the BOM generator + if err = params.bomGenerator.WithOptions(bomGenOptions...).PrepareGenerator(); err != nil { return cmdResults.AddGeneralError(fmt.Errorf("failed to prepare the BOM generator: %s", err.Error()), false) } // Initialize the SCA scan strategy - scanGraphParams, err := params.ToXrayScanGraphParams() - if err != nil { - return cmdResults.AddGeneralError(fmt.Errorf("failed to create scan graph params: %s", err.Error()), false) - } - if err = params.scaScanStrategy.WithOptions(scanGraphStrategy.WithParams(scanGraphParams)).PrepareStrategy(); err != nil { + if err = params.scaScanStrategy.WithOptions(scanOptions...).PrepareStrategy(); err != nil { return cmdResults.AddGeneralError(fmt.Errorf("failed to prepare the SCA scan strategy: %s", err.Error()), false) } populateScanTargets(cmdResults, params) return } +func getScanLogicOptions(params *AuditParams) (bomGenOptions []bom.SbomGeneratorOption, scanOptions []scan.SbomScanOption, err error) { + // Bom Generators Options + buildParams, err := params.ToBuildInfoBomGenParams() + if err != nil { + return nil, nil, fmt.Errorf("failed to create build info params: %w", err) + } + bomGenOptions = []bom.SbomGeneratorOption{ + // Build Info Bom Generator Options + buildinfo.WithParams(buildParams), + } + // Scan Strategies Options + scanGraphParams, err := params.ToXrayScanGraphParams() + if err != nil { + return nil, nil, fmt.Errorf("failed to create scan graph params: %w", err) + } + serverDetails, err := params.ServerDetails() + if err != nil { + return nil, nil, fmt.Errorf("failed to get server details: %w", err) + } + scanOptions = []scan.SbomScanOption{ + // Xray Scan Graph Strategy Options + scanGraphStrategy.WithParams(scanGraphParams), + // Catalog Enrich Strategy Options + enrich.WithParams(serverDetails, params.resultsContext.ProjectKey), + } + return bomGenOptions, scanOptions, nil +} + func initAuditCmdResults(params *AuditParams) (cmdResults *results.SecurityCommandResults) { cmdResults = results.NewCommandResults(utils.SourceCode) // Initialize general information diff --git a/commands/upload/uploadcdx_test.go b/commands/upload/uploadcdx_test.go index 3f662a544..1569bcfdd 100644 --- a/commands/upload/uploadcdx_test.go +++ b/commands/upload/uploadcdx_test.go @@ -24,7 +24,7 @@ func TestValidateInputFile(t *testing.T) { assert.NoError(t, utils.SaveCdxContentToFile(validCdxFilePath, cdx)) // create a file with not valid extension noCdxExtensionFile := filepath.Join(tempDirPath, "invalid_results.json") - assert.NoError(t, utils.DumpContentToFile([]byte("This is not a valid CycloneDX file."), tempDirPath, "invalid_results", "", -1)) + assert.NoError(t, utils.DumpContentToFile([]byte("This is not a valid CycloneDX file."), tempDirPath, "invalid_results", ".json", -1)) tests := []struct { name string diff --git a/go.mod b/go.mod index c77d589b1..046723b39 100644 --- a/go.mod +++ b/go.mod @@ -115,9 +115,9 @@ 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.20250629142537-bb24db402fe1 +replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20250707095624-7062538a0961 -replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20250702103155-efd0c6adf4f5 +replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20250707105555-807262eb0f88 replace github.com/jfrog/jfrog-cli-artifactory => github.com/jfrog/jfrog-cli-artifactory v0.3.3-0.20250623095509-b3fe2c4681ad diff --git a/go.sum b/go.sum index 1f78b1b90..782c98e46 100644 --- a/go.sum +++ b/go.sum @@ -126,10 +126,10 @@ github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYL github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w= github.com/jfrog/jfrog-cli-artifactory v0.3.3-0.20250623095509-b3fe2c4681ad h1:cnbcCK0VTHdLdmmv/9fYTRjuR1ewrYBW/S87pVE+d+s= github.com/jfrog/jfrog-cli-artifactory v0.3.3-0.20250623095509-b3fe2c4681ad/go.mod h1:hnXaevmDyQpyhpH5kwDufIjUUXXuKs54i+AX2CEywKE= -github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20250702103155-efd0c6adf4f5 h1:+tbqR721+c91RrwcQkvye9RkvbDfH3W1LDU5juo0sNw= -github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20250702103155-efd0c6adf4f5/go.mod h1:BkDHfCVecjcRNC+aqmHtTrKe+/K8MyjzmHYe5rER5Yg= -github.com/jfrog/jfrog-client-go v1.28.1-0.20250629142537-bb24db402fe1 h1:0t6dQHoalUDNVrfZujD3iCmDGLDl+ndHclFkmONSpq0= -github.com/jfrog/jfrog-client-go v1.28.1-0.20250629142537-bb24db402fe1/go.mod h1:1v0eih4thdPA4clBo9TuvAMT25sGDr1IQJ81DXQ/lBY= +github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20250707105555-807262eb0f88 h1:OAhG6yUBIEKYW66Oe++8S8K6wsOgHhyr0/SEUkkF6oQ= +github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20250707105555-807262eb0f88/go.mod h1:wzsMWhIJJgaZMi4CJk1uE7cZqw9AMI/ijw2Bb8UQjF0= +github.com/jfrog/jfrog-client-go v1.28.1-0.20250707095624-7062538a0961 h1:JI3qV665s4RlvQ3K4t7yXJ8hqvfFF4TVRwmaOF4zCls= +github.com/jfrog/jfrog-client-go v1.28.1-0.20250707095624-7062538a0961/go.mod h1:1v0eih4thdPA4clBo9TuvAMT25sGDr1IQJ81DXQ/lBY= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= diff --git a/sca/scan/enrich/runner.go b/sca/scan/enrich/runner.go new file mode 100644 index 000000000..d67850bad --- /dev/null +++ b/sca/scan/enrich/runner.go @@ -0,0 +1,66 @@ +package enrich + +import ( + "errors" + "fmt" + + "github.com/CycloneDX/cyclonedx-go" + + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-client-go/utils/log" + "github.com/jfrog/jfrog-client-go/xray/services" + + "github.com/jfrog/jfrog-cli-security/sca/scan" + "github.com/jfrog/jfrog-cli-security/utils/catalog" +) + +type EnrichScanStrategy struct { + serverDetails *config.ServerDetails + projectKey string +} + +func NewEnrichScanStrategy() *EnrichScanStrategy { + return &EnrichScanStrategy{} +} + +func WithParams(serverDetails *config.ServerDetails, projectKey string) scan.SbomScanOption { + return func(sss scan.SbomScanStrategy) { + if ess, ok := sss.(*EnrichScanStrategy); ok { + ess.serverDetails = serverDetails + ess.projectKey = projectKey + } + } +} + +func (ess *EnrichScanStrategy) WithOptions(options ...scan.SbomScanOption) scan.SbomScanStrategy { + for _, option := range options { + option(ess) + } + return ess +} + +func (ess *EnrichScanStrategy) PrepareStrategy() (err error) { + catalogManager, err := catalog.CreateCatalogServiceManager(ess.serverDetails, catalog.WithScopedProjectKey(ess.projectKey)) + if err != nil { + return fmt.Errorf("failed to create catalog service manager: %w", err) + } + catalogVersion, err := catalogManager.GetVersion() + if err != nil { + return fmt.Errorf("failed to get catalog version: %w", err) + } + log.Debug(fmt.Sprintf("Catalog version: %s", catalogVersion)) + return +} + +func (ess *EnrichScanStrategy) SbomEnrichTask(target *cyclonedx.BOM) (enriched *cyclonedx.BOM, violations []services.Violation, err error) { + catalogManager, err := catalog.CreateCatalogServiceManager(ess.serverDetails, catalog.WithScopedProjectKey(ess.projectKey)) + if err != nil { + return nil, []services.Violation{}, fmt.Errorf("failed to create catalog service manager: %w", err) + } + enriched, err = catalogManager.Enrich(target) + return +} + +func (ess *EnrichScanStrategy) DeprecatedScanTask(target *cyclonedx.BOM) (techResults services.ScanResponse, err error) { + return services.ScanResponse{}, errors.New("EnrichScanStrategy does not support DeprecatedScanTask") +} diff --git a/sca/scan/scascan.go b/sca/scan/scascan.go index 0de5f1aec..a407e9cf0 100644 --- a/sca/scan/scascan.go +++ b/sca/scan/scascan.go @@ -28,6 +28,7 @@ type SbomScanStrategy interface { // TODO: This method is deprecated and only used for backward compatibility until the new BOM can contain all the information scanResponse contains. // Missing attributes: // - ExtendedInformation (JfrogResearchInformation): ShortDescription, FullDescription, frogResearchSeverityReasons, Remediation + // - Binary (Docker) indexer attributes (needed for Scan Graph) DeprecatedScanTask(target *cyclonedx.BOM) (services.ScanResponse, error) // Perform a Scan on the given SBOM and return the enriched CycloneDX BOM and calculated violations. (Violations will be moved at the future to the end of command) SbomEnrichTask(target *cyclonedx.BOM) (*cyclonedx.BOM, []services.Violation, error) diff --git a/utils/catalog/catalogmanager.go b/utils/catalog/catalogmanager.go new file mode 100644 index 000000000..b08da3677 --- /dev/null +++ b/utils/catalog/catalogmanager.go @@ -0,0 +1,45 @@ +package catalog + +import ( + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-client-go/catalog" + clientconfig "github.com/jfrog/jfrog-client-go/config" +) + +// Options for creating an Catalog service manager. +type CatalogManagerOption func(f *catalog.CatalogServicesManager) + +// Global reference to the project key, used for API endpoints that require it for authentication +func WithScopedProjectKey(projectKey string) CatalogManagerOption { + return func(f *catalog.CatalogServicesManager) { + f.SetProjectKey(projectKey) + } +} + +func CreateCatalogServiceManager(serverDetails *config.ServerDetails, options ...CatalogManagerOption) (manager *catalog.CatalogServicesManager, err error) { + certsPath, err := coreutils.GetJfrogCertsDir() + if err != nil { + return + } + catalogDetails, err := serverDetails.CreateCatalogAuthConfig() + if err != nil { + return + } + serviceConfig, err := clientconfig.NewConfigBuilder(). + SetServiceDetails(catalogDetails). + SetCertificatesPath(certsPath). + SetInsecureTls(serverDetails.InsecureTls). + Build() + if err != nil { + return + } + manager, err = catalog.New(serviceConfig) + if err != nil { + return nil, err + } + for _, option := range options { + option(manager) + } + return +}