Skip to content

Commit ccbf089

Browse files
Merge pull request #1231 from Checkmarx/feature/ast-105749-sbom-scan
Feature/ast 105749 SBOM scan (AST-105749)
2 parents 7356f77 + faabdcf commit ccbf089

File tree

5 files changed

+172
-9
lines changed

5 files changed

+172
-9
lines changed

internal/commands/scan.go

Lines changed: 93 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package commands
33
import (
44
"archive/zip"
55
"encoding/json"
6+
"encoding/xml"
67
"fmt"
78
"io"
89
"io/fs"
@@ -118,7 +119,11 @@ const (
118119
ScsRepoWarningMsg = "SCS scan warning: Unable to start Scorecard scan due to missing required flags, please include in the ast-cli arguments: " +
119120
"--scs-repo-url your_repo_url --scs-repo-token your_repo_token"
120121
ScsScorecardUnsupportedHostWarningMsg = "SCS scan warning: Unable to run Scorecard scanner due to unsupported repo host. Currently, Scorecard can only run on GitHub Cloud repos."
121-
BranchPrimaryPrefix = "--branch-primary="
122+
123+
jsonExt = ".json"
124+
xmlExt = ".xml"
125+
sbomScanTypeErrMsg = "The --sbom-only flag can only be used when the scan type is sca"
126+
BranchPrimaryPrefix = "--branch-primary="
122127
)
123128

124129
var (
@@ -857,6 +862,9 @@ func scanCreateSubCommand(
857862
createScanCmd.PersistentFlags().Bool(commonParams.ContainersExcludeNonFinalStagesFlag, false, "Scan only the final deployable image")
858863
createScanCmd.PersistentFlags().String(commonParams.ContainersImageTagFilterFlag, "", "Exclude images by image name and/or tag, ex: \"*dev\"")
859864

865+
// reading sbom-only flag
866+
createScanCmd.PersistentFlags().Bool(commonParams.SbomFlag, false, "Scan only the specified SBOM file (supported formats xml or json)")
867+
860868
return createScanCmd
861869
}
862870

@@ -1139,6 +1147,7 @@ func addScaScan(cmd *cobra.Command, resubmitConfig []wrappers.Config, hasContain
11391147
scaMapConfig := make(map[string]interface{})
11401148
scaConfig := wrappers.ScaConfig{}
11411149
scaMapConfig[resultsMapType] = commonParams.ScaType
1150+
isSbom, _ := cmd.PersistentFlags().GetBool(commonParams.SbomFlag)
11421151
scaConfig.Filter, _ = cmd.Flags().GetString(commonParams.ScaFilterFlag)
11431152
scaConfig.LastSastScanTime, _ = cmd.Flags().GetString(commonParams.LastSastScanTime)
11441153
scaConfig.PrivatePackageVersion, _ = cmd.Flags().GetString(commonParams.ScaPrivatePackageVersionFlag)
@@ -1157,6 +1166,7 @@ func addScaScan(cmd *cobra.Command, resubmitConfig []wrappers.Config, hasContain
11571166
}
11581167
}
11591168
}
1169+
scaConfig.SBom = strconv.FormatBool(isSbom)
11601170
scaMapConfig[resultsMapValue] = &scaConfig
11611171
return scaMapConfig
11621172
}
@@ -1372,6 +1382,8 @@ func validateScanTypes(cmd *cobra.Command, jwtWrapper wrappers.JWTWrapper, featu
13721382
var scanTypes []string
13731383
var SCSScanTypes []string
13741384

1385+
isSbomScan, _ := cmd.PersistentFlags().GetBool(commonParams.SbomFlag)
1386+
13751387
allowedEngines, err := jwtWrapper.GetAllowedEngines(featureFlagsWrapper)
13761388
if err != nil {
13771389
err = errors.Errorf("Error validating scan types: %v", err)
@@ -1387,6 +1399,20 @@ func validateScanTypes(cmd *cobra.Command, jwtWrapper wrappers.JWTWrapper, featu
13871399
userSCSScanTypes = strings.Replace(strings.ToLower(userSCSScanTypes), commonParams.SCSEnginesFlag, commonParams.ScsType, 1)
13881400

13891401
scanTypes = strings.Split(userScanTypes, ",")
1402+
1403+
// check scan-types, when sbom-only flag is used
1404+
if isSbomScan {
1405+
if len(scanTypes) > 1 {
1406+
err = errors.Errorf(sbomScanTypeErrMsg)
1407+
return err
1408+
}
1409+
1410+
if scanTypes[0] != "sca" {
1411+
err = errors.Errorf(sbomScanTypeErrMsg)
1412+
return err
1413+
}
1414+
}
1415+
13901416
for _, scanType := range scanTypes {
13911417
if !allowedEngines[scanType] {
13921418
keys := reflect.ValueOf(allowedEngines).MapKeys()
@@ -1402,11 +1428,19 @@ func validateScanTypes(cmd *cobra.Command, jwtWrapper wrappers.JWTWrapper, featu
14021428
return err
14031429
}
14041430
} else {
1405-
for k := range allowedEngines {
1406-
scanTypes = append(scanTypes, k)
1431+
if isSbomScan {
1432+
if allowedEngines["sca"] {
1433+
// for sbom-flag, setting scan-type as only "sca"
1434+
scanTypes = append(scanTypes, "sca")
1435+
} else {
1436+
return errors.Errorf("sbom needs sca engine to be allowed")
1437+
}
1438+
} else {
1439+
for k := range allowedEngines {
1440+
scanTypes = append(scanTypes, k)
1441+
}
14071442
}
14081443
}
1409-
14101444
actualScanTypes = strings.Join(scanTypes, ",")
14111445
actualScanTypes = strings.Replace(strings.ToLower(actualScanTypes), commonParams.IacType, commonParams.KicsType, 1)
14121446

@@ -1705,8 +1739,24 @@ func getUploadURLFromSource(cmd *cobra.Command, uploadsWrapper wrappers.UploadsW
17051739
scaResolverPath, _ := cmd.Flags().GetString(commonParams.ScaResolverFlag)
17061740

17071741
scaResolverParams, scaResolver := getScaResolverFlags(cmd)
1708-
1709-
zipFilePath, directoryPath, err := definePathForZipFileOrDirectory(cmd)
1742+
isSbom, _ := cmd.PersistentFlags().GetBool(commonParams.SbomFlag)
1743+
var directoryPath string
1744+
if isSbom {
1745+
sbomFile, _ := cmd.Flags().GetString(commonParams.SourcesFlag)
1746+
isValid, err := isValidJSONOrXML(sbomFile)
1747+
if err != nil {
1748+
return "", "", errors.Wrapf(err, "%s: Input in bad format", failedCreating)
1749+
}
1750+
if !isValid {
1751+
return "", "", errors.Wrapf(err, "%s: Input in bad format", failedCreating)
1752+
}
1753+
zipFilePath, err = util.CompressFile(sbomFile, "sbomFileCompress", directoryCreationPrefix)
1754+
if err != nil {
1755+
return "", "", errors.Wrapf(err, "%s: Input in bad format", failedCreating)
1756+
}
1757+
} else {
1758+
zipFilePath, directoryPath, err = definePathForZipFileOrDirectory(cmd)
1759+
}
17101760

17111761
if zipFilePath != "" && scaResolverPath != "" {
17121762
return "", "", errors.New("Scanning Zip files is not supported by ScaResolver.Please use non-zip source")
@@ -1766,7 +1816,9 @@ func getUploadURLFromSource(cmd *cobra.Command, uploadsWrapper wrappers.UploadsW
17661816
}
17671817
}
17681818
} else {
1769-
zipFilePath, dirPathErr = compressFolder(directoryPath, sourceDirFilter, userIncludeFilter, scaResolver)
1819+
if !isSbom {
1820+
zipFilePath, dirPathErr = compressFolder(directoryPath, sourceDirFilter, userIncludeFilter, scaResolver)
1821+
}
17701822
}
17711823
if dirPathErr != nil {
17721824
return "", "", dirPathErr
@@ -1780,8 +1832,10 @@ func getUploadURLFromSource(cmd *cobra.Command, uploadsWrapper wrappers.UploadsW
17801832
}
17811833
}
17821834

1783-
if zipFilePath != "" {
1835+
if zipFilePath != "" && !isSbom {
17841836
return uploadZip(uploadsWrapper, zipFilePath, unzip, userProvidedZip, featureFlagsWrapper)
1837+
} else if zipFilePath != "" && isSbom {
1838+
return uploadZip(uploadsWrapper, zipFilePath, unzip, false, featureFlagsWrapper)
17851839
}
17861840
return preSignedURL, zipFilePath, nil
17871841
}
@@ -3027,8 +3081,9 @@ func deprecatedFlagValue(cmd *cobra.Command, deprecatedFlagKey, inUseFlagKey str
30273081
}
30283082

30293083
func validateCreateScanFlags(cmd *cobra.Command) error {
3084+
isSbomScan, _ := cmd.PersistentFlags().GetBool(commonParams.SbomFlag)
30303085
branch := strings.TrimSpace(viper.GetString(commonParams.BranchKey))
3031-
if branch == "" {
3086+
if branch == "" && !isSbomScan {
30323087
return errors.Errorf("%s: Please provide a branch", failedCreating)
30333088
}
30343089
exploitablePath, _ := cmd.Flags().GetString(commonParams.ExploitablePathFlag)
@@ -3158,3 +3213,32 @@ func createMinimalZipFile() (string, error) {
31583213

31593214
return outputFile.Name(), nil
31603215
}
3216+
3217+
func isValidJSONOrXML(path string) (bool, error) {
3218+
ext := strings.ToLower(filepath.Ext(path))
3219+
if ext != jsonExt && ext != xmlExt {
3220+
return false, fmt.Errorf("not a JSON/XML file, provide valid JSON/XMl file")
3221+
}
3222+
3223+
data, err := ioutil.ReadFile(path)
3224+
if err != nil {
3225+
return false, fmt.Errorf("failed to read file: %w", err)
3226+
}
3227+
3228+
switch ext {
3229+
case jsonExt:
3230+
var js interface{}
3231+
if err := json.Unmarshal(data, &js); err != nil {
3232+
return false, fmt.Errorf("invalid JSON format. %w", err) // Invalid JSON
3233+
}
3234+
case xmlExt:
3235+
var x interface{}
3236+
if err := xml.Unmarshal(data, &x); err != nil {
3237+
return false, fmt.Errorf("invalid XML format.%w", err) // Invalid XML
3238+
}
3239+
default:
3240+
return false, nil
3241+
}
3242+
3243+
return true, nil
3244+
}

internal/commands/scan_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,7 @@ func TestAddScaScan(t *testing.T) {
787787
ExploitablePath: "true",
788788
LastSastScanTime: "1",
789789
PrivatePackageVersion: "1.1.1",
790+
SBom: "false",
790791
}
791792
scaMapConfig := make(map[string]interface{})
792793
scaMapConfig[resultsMapType] = commonParams.ScaType
@@ -2412,3 +2413,32 @@ func Test_parseArgs(t *testing.T) {
24122413
}
24132414
}
24142415
}
2416+
2417+
func Test_isValidJSONOrXML(t *testing.T) {
2418+
tests := []struct {
2419+
description string
2420+
inputPath string
2421+
output bool
2422+
}{
2423+
{"wrong extension", "somefile.txt", false},
2424+
{"wrong json file", "wrongfilepath.json", false},
2425+
{"wrong xml file", "wrongfilepath.xml", false},
2426+
{"correct file", "data/package.json", true},
2427+
}
2428+
2429+
for _, test := range tests {
2430+
isValid, _ := isValidJSONOrXML(test.inputPath)
2431+
if isValid != test.output {
2432+
t.Errorf(" test case failed for params %v", test)
2433+
}
2434+
}
2435+
}
2436+
2437+
func Test_CreateScanWithSbomFlag(t *testing.T) {
2438+
err := execCmdNotNilAssertion(
2439+
t,
2440+
"scan", "create", "--project-name", "newProject", "-s", "data/sbom.json", "--branch", "dummy_branch", "--sbom-only",
2441+
)
2442+
2443+
assert.ErrorContains(t, err, "Failed creating a scan: Input in bad format: failed to read file:")
2444+
}

internal/params/flags.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,9 @@ const (
231231
ContainersImageTagFilterFlag = "containers-image-tag-filter"
232232
ContainersPackageFilterFlag = "containers-package-filter"
233233
ContainersExcludeNonFinalStagesFlag = "containers-exclude-non-final-stages"
234+
235+
// SBOM - flag
236+
SbomFlag = "sbom-only"
234237
)
235238

236239
// Parameter values

internal/wrappers/scans.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ type ScaConfig struct {
143143
LastSastScanTime string `json:"LastSastScanTime,omitempty"`
144144
PrivatePackageVersion string `json:"privatePackageVersion,omitempty"`
145145
EnableContainersScan bool `json:"enableContainersScan,omitempty"`
146+
SBom string `json:"sbom,omitempty"`
146147
}
147148
type ContainerConfig struct {
148149
FilesFilter string `json:"filesFilter,omitempty"`

test/integration/scan_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2383,3 +2383,48 @@ func TestCreateScan_WithScaResolver_ZipSource_Fail(t *testing.T) {
23832383
err, _ := executeCommand(t, args...)
23842384
assert.Error(t, err, "Scanning Zip files is not supported by ScaResolver.Please use non-zip source")
23852385
}
2386+
2387+
func TestCreateScan_SbomScanForInvalidScanTypes(t *testing.T) {
2388+
args := []string{
2389+
"scan", "create",
2390+
flag(params.ProjectName), "random_proj",
2391+
flag(params.SourcesFlag), "data/project-with-directory-symlink",
2392+
flag(params.ScanTypes), "sast,sca",
2393+
flag(params.BranchFlag), "dummy_branch",
2394+
flag(params.SbomFlag),
2395+
}
2396+
2397+
err, _ := executeCommand(t, args...)
2398+
assert.Error(t, err, "The --sbom-only flag can only be used when the scan type is sca")
2399+
2400+
}
2401+
2402+
func TestCreateScan_SbomScanForInvalidFileExtension(t *testing.T) {
2403+
args := []string{
2404+
"scan", "create",
2405+
flag(params.ProjectName), "random_proj",
2406+
flag(params.SourcesFlag), "data/project-with-directory-symlink",
2407+
flag(params.ScanTypes), "sca",
2408+
flag(params.BranchFlag), "dummy_branch",
2409+
flag(params.SbomFlag),
2410+
}
2411+
2412+
err, _ := executeCommand(t, args...)
2413+
assert.Error(t, err, "Failed creating a scan: Input in bad format: not a JSON/XML file, provide valid JSON/XMl file")
2414+
2415+
}
2416+
2417+
func TestCreateScan_SbomScanForNotExistingFile(t *testing.T) {
2418+
args := []string{
2419+
"scan", "create",
2420+
flag(params.ProjectName), "random_proj",
2421+
flag(params.SourcesFlag), "data/sbom.json",
2422+
flag(params.ScanTypes), "sca",
2423+
flag(params.BranchFlag), "dummy_branch",
2424+
flag(params.SbomFlag),
2425+
}
2426+
2427+
err, _ := executeCommand(t, args...)
2428+
assert.ErrorContains(t, err, "Failed creating a scan: Input in bad format: failed to read file:")
2429+
2430+
}

0 commit comments

Comments
 (0)