Skip to content

Commit

Permalink
AST-31546 Add support to GL-Sca report creation (#788)
Browse files Browse the repository at this point in the history
* AST-31546 Add support to GL-Sca report creation

* Revert test

* Update result_test.go

* Update result_test.go

Delete whitespace on row 714

* Revert change

* Revert change

* Update result_test.go
  • Loading branch information
margaritalm authored Jun 30, 2024
1 parent b01f71d commit 711edd2
Show file tree
Hide file tree
Showing 7 changed files with 462 additions and 41 deletions.
186 changes: 175 additions & 11 deletions internal/commands/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ const (
lowLabel = "low"
infoLabel = "info"
sonarTypeLabel = "_sonar"
glSastTypeLobel = ".gl-sast-report"
glSastTypeLabel = ".gl-sast-report"
glScaTypeLabel = ".gl-sca-report"
directoryPermission = 0700
infoSonar = "INFO"
lowSonar = "MINOR"
Expand Down Expand Up @@ -107,7 +108,8 @@ var summaryFormats = []string{
printer.FormatPDF,
printer.FormatSummaryMarkdown,
printer.FormatSbom,
printer.FormatGL,
printer.FormatGLSast,
printer.FormatGLSca,
}

var filterResultsListFlagUsage = fmt.Sprintf(
Expand Down Expand Up @@ -229,7 +231,8 @@ func resultShowSubCommand(
printer.FormatSbom,
printer.FormatPDF,
printer.FormatSummaryMarkdown,
printer.FormatGL,
printer.FormatGLSast,
printer.FormatGLSca,
)
resultShowCmd.PersistentFlags().String(commonParams.ReportFormatPdfToEmailFlag, "", pdfToEmailFlagDescription)
resultShowCmd.PersistentFlags().String(commonParams.ReportSbomFormatFlag, defaultSbomOption, sbomReportFlagDescription)
Expand Down Expand Up @@ -1142,10 +1145,15 @@ func createReport(format,
jsonRpt := createTargetName(targetFile, targetPath, printer.FormatJSON)
return exportJSONResults(jsonRpt, results)
}
if printer.IsFormat(format, printer.FormatGL) {
jsonRpt := createTargetName(fmt.Sprintf("%s%s", targetFile, glSastTypeLobel), targetPath, printer.FormatJSON)
if printer.IsFormat(format, printer.FormatGLSast) {
jsonRpt := createTargetName(fmt.Sprintf("%s%s", targetFile, glSastTypeLabel), targetPath, printer.FormatJSON)
return exportGlSastResults(jsonRpt, results, summary)
}
if printer.IsFormat(format, printer.FormatGLSca) {
jsonRpt := createTargetName(fmt.Sprintf("%s%s", targetFile, glScaTypeLabel), targetPath, printer.FormatJSON)
return exportGlScaResults(jsonRpt, results, summary)
}

if printer.IsFormat(format, printer.FormatSummaryConsole) {
return writeConsoleSummary(summary)
}
Expand Down Expand Up @@ -1300,30 +1308,80 @@ func exportGlSastResults(targetFile string, results *wrappers.ScanResultsCollect
glSast.Vulnerabilities = []wrappers.GlVulnerabilities{}
err := addScanToGlSastReport(summary, glSast)
if err != nil {
return errors.Wrapf(err, "%s: failed to add scan to gl sast report", failedListingResults)
return errors.Wrapf(err, "%s: failed to add scan to gl-sast report", failedListingResults)
}
convertCxResultToGlVulnerability(results, glSast, summary.BaseURI)
convertCxResultToGlSastVulnerability(results, glSast, summary.BaseURI)
resultsJSON, err := json.Marshal(glSast)
if err != nil {
return errors.Wrapf(err, "%s: failed to serialize gl sast report ", failedListingResults)
return errors.Wrapf(err, "%s: failed to serialize gl-sast report ", failedListingResults)
}
f, err := os.Create(targetFile)
if err != nil {
return errors.Wrapf(err, "%s: failed to create target file ", failedListingResults)
}
defer f.Close()
_, _ = fmt.Fprintln(f, string(resultsJSON))
return nil
}

func exportGlScaResults(targetFile string, results *wrappers.ScanResultsCollection, summary *wrappers.ResultSummary) error {
log.Println("Creating Gl-sca Report: ", targetFile)
glScaResult := &wrappers.GlScaResultsCollection{}
err := addScanToGlScaReport(summary, glScaResult)
if err != nil {
return errors.Wrapf(err, "%s: failed to denerate GL-Sca report ", failedListingResults)
}
convertCxResultToGlScaVulnerability(results, glScaResult)
convertCxResultToGlScaFiles(results, glScaResult)
resultsJSON, err := json.Marshal(glScaResult)
if err != nil {
return errors.Wrapf(err, "%s: failed to serialize GL-Sca report ", failedListingResults)
}
f, err := os.Create(targetFile)
if err != nil {
return errors.Wrapf(err, "%s: failed to create target file ", failedListingResults)
}
_, _ = fmt.Fprintln(f, string(resultsJSON))
defer f.Close()

return nil
}

func addScanToGlScaReport(summary *wrappers.ResultSummary, glScaResult *wrappers.GlScaResultsCollection) error {
createdAt, err := time.Parse(summaryCreatedAtLayout, summary.CreatedAt)
if err != nil {
return err
}

glScaResult.Schema = wrappers.ScaSchema
glScaResult.Version = wrappers.SchemaVersion
glScaResult.Scan.Analyzer.VendorGlSCA.VendorGlname = wrappers.AnalyzerScaID
glScaResult.Scan.Analyzer.Name = wrappers.AnalyzerScaID
glScaResult.Scan.Analyzer.Name = wrappers.AnalyzerScaID
glScaResult.Scan.Analyzer.ID = wrappers.ScannerID
glScaResult.Scan.Scanner.ID = wrappers.ScannerID
glScaResult.Scan.Scanner.Name = wrappers.AnalyzerScaID
glScaResult.Scan.Scanner.VendorGlSCA.VendorGlname = wrappers.AnalyzerScaID
glScaResult.Scan.Status = commonParams.Success
glScaResult.Scan.Type = wrappers.ScannerType
glScaResult.Scan.StartTime = createdAt.Format(glTimeFormat)
glScaResult.Scan.EndTime = createdAt.Format(glTimeFormat)
glScaResult.Scan.Scanner.Name = wrappers.AnalyzerScaID
glScaResult.Scan.Scanner.VersionGlSca = commonParams.Version
glScaResult.Scan.Analyzer.VersionGlSca = commonParams.Version

return nil
}

func addScanToGlSastReport(summary *wrappers.ResultSummary, glSast *wrappers.GlSastResultsCollection) error {
createdAt, err := time.Parse(summaryCreatedAtLayout, summary.CreatedAt)
if err != nil {
return err
}

glSast.Scan = wrappers.ScanGlReport{}
glSast.Schema = "https://gitlab.com/gitlab-org/gitlab/-/raw/master/lib/gitlab/ci/parsers/security/validators/schemas/15.0.0/sast-report-format.json"
glSast.Version = "15.0.0"
glSast.Schema = wrappers.SastSchema
glSast.Version = wrappers.SastSchemaVersion
glSast.Scan.Analyzer.URL = wrappers.AnalyzerURL
glSast.Scan.Analyzer.Name = wrappers.VendorName
glSast.Scan.Analyzer.Vendor.Name = wrappers.VendorName
Expand Down Expand Up @@ -1623,14 +1681,29 @@ func convertCxResultsToSarif(results *wrappers.ScanResultsCollection) *wrappers.
return sarif
}

func convertCxResultToGlVulnerability(results *wrappers.ScanResultsCollection, glSast *wrappers.GlSastResultsCollection, summaryBaseURI string) {
func convertCxResultToGlSastVulnerability(results *wrappers.ScanResultsCollection, glSast *wrappers.GlSastResultsCollection, summaryBaseURI string) {
for _, result := range results.Results {
if strings.TrimSpace(result.Type) == commonParams.SastType {
glSast = parseGlSastVulnerability(result, glSast, summaryBaseURI)
}
}
}

func convertCxResultToGlScaVulnerability(results *wrappers.ScanResultsCollection, glScaResult *wrappers.GlScaResultsCollection) {
for _, result := range results.Results {
if strings.TrimSpace(result.Type) == commonParams.ScaType {
glScaResult = parseGlscaVulnerability(result, glScaResult)
}
}
}

func convertCxResultToGlScaFiles(results *wrappers.ScanResultsCollection, glScaResult *wrappers.GlScaResultsCollection) {
for _, result := range results.Results {
if strings.TrimSpace(result.Type) == commonParams.ScaType {
glScaResult = parseGlScaFiles(result, glScaResult)
}
}
}
func parseGlSastVulnerability(result *wrappers.ScanResult, glSast *wrappers.GlSastResultsCollection, summaryBaseURI string) *wrappers.GlSastResultsCollection {
queryName := result.ScanResultData.QueryName
fileName := result.ScanResultData.Nodes[0].FileName
Expand Down Expand Up @@ -1685,6 +1758,97 @@ func parseGlSastVulnerability(result *wrappers.ScanResult, glSast *wrappers.GlSa
})
return glSast
}
func parseGlscaVulnerability(result *wrappers.ScanResult, glDependencyResult *wrappers.GlScaResultsCollection) *wrappers.GlScaResultsCollection {
if result.ScanResultData.ScaPackageCollection != nil {
glDependencyResult.Vulnerabilities = append(glDependencyResult.Vulnerabilities, wrappers.GlScaDepVulnerabilities{
ID: result.ID,
Name: result.VulnerabilityDetails.CveName,
Description: result.Description,
Severity: cases.Title(language.English).String(result.Severity),
Solution: result.ScanResultData.RecommendedVersion,
Identifiers: collectScaPackageData(result),
Links: collectScaPackageLinks(result),
TrackingDep: wrappers.TrackingDep{
Items: collectScaPackageItemsDep(result),
},
Flags: make([]string, 0),
LocationDep: wrappers.GlScaDepVulnerabilityLocation{
File: parseGlDependencyLocation(result),
Dependency: wrappers.ScaDependencyLocation{
Package: wrappers.PackageName{Name: result.ScanResultData.PackageIdentifier},
ScaDependencyLocationVersion: "",
Direct: result.ScanResultData.ScaPackageCollection.IsDirectDependency,
ScaDependencyPath: result.ScanResultData.Line,
},
},
})
}
return glDependencyResult
}
func parseGlDependencyLocation(result *wrappers.ScanResult) string {
var location string
if result != nil && result.ScanResultData.ScaPackageCollection != nil && result.ScanResultData.ScaPackageCollection.Locations != nil {
location = *result.ScanResultData.ScaPackageCollection.Locations[0]
} else {
location = ""
}
return (location)
}
func parseGlScaFiles(result *wrappers.ScanResult, glScaResult *wrappers.GlScaResultsCollection) *wrappers.GlScaResultsCollection {
if result.ScanResultData.ScaPackageCollection != nil && result.ScanResultData.ScaPackageCollection.Locations != nil {
glScaResult.ScaDependencyFiles = append(glScaResult.ScaDependencyFiles, wrappers.ScaDependencyFile{
Path: *result.ScanResultData.ScaPackageCollection.Locations[0],
PackageManager: result.ScanResultData.ScaPackageCollection.ID,
Dependencies: collectScaFileLocations(result),
})
}
return glScaResult
}
func collectScaFileLocations(result *wrappers.ScanResult) []wrappers.ScaDependencyLocation {
allScaIdentifierLocations := []wrappers.ScaDependencyLocation{}
for _, packageInfo := range result.ScanResultData.PackageData {
allScaIdentifierLocations = append(allScaIdentifierLocations, wrappers.ScaDependencyLocation{
Package: wrappers.PackageName{
Name: packageInfo.Type,
},
ScaDependencyLocationVersion: packageInfo.URL,
Direct: true,
ScaDependencyPath: result.ScanResultData.Line,
})
}
return allScaIdentifierLocations
}
func collectScaPackageItemsDep(result *wrappers.ScanResult) []wrappers.ItemDep {
allScaPackageItemDep := []wrappers.ItemDep{}
allScaPackageItemDep = append(allScaPackageItemDep, wrappers.ItemDep{
Signature: []wrappers.SignatureDep{{Algorithm: "SCA-Algorithm ", Value: "NA"}},
File: result.VulnerabilityDetails.CveName,
EndLine: 0,
StartLine: 0,
})
return allScaPackageItemDep
}
func collectScaPackageLinks(result *wrappers.ScanResult) []wrappers.LinkDep {
allScaPackageLinks := []wrappers.LinkDep{}
for _, packageInfo := range result.ScanResultData.PackageData {
allScaPackageLinks = append(allScaPackageLinks, wrappers.LinkDep{
Name: packageInfo.Type,
URL: packageInfo.URL,
})
}
return allScaPackageLinks
}
func collectScaPackageData(result *wrappers.ScanResult) []wrappers.IdentifierDep {
allIdentifierDep := []wrappers.IdentifierDep{}
for _, packageInfo := range result.ScanResultData.PackageData {
allIdentifierDep = append(allIdentifierDep, wrappers.IdentifierDep{
Type: packageInfo.Type,
Value: packageInfo.URL,
Name: packageInfo.URL,
})
}
return allIdentifierDep
}

func convertCxResultsToSonar(results *wrappers.ScanResultsCollection) *wrappers.ScanResultsSonar {
var sonar = new(wrappers.ScanResultsSonar)
Expand Down
83 changes: 60 additions & 23 deletions internal/commands/result_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -530,32 +530,19 @@ func TestSBOMReportXMLWithProxy(t *testing.T) {
func TestRunGetResultsByScanIdGLFormat(t *testing.T) {
execCmdNilAssertion(t, "results", "show", "--scan-id", "MOCK", "--report-format", "gl-sast")
// Run test for gl-sast report type
os.Remove(fmt.Sprintf("%s.%s", fileName, printer.FormatGL))
os.Remove(fmt.Sprintf("%s.%s", fileName, printer.FormatGLSast))
}

func TestRunGetResultsByScanIdGLFormat_NoVulnerabilities_Success(t *testing.T) {
// Execute the command and perform nil assertion
execCmdNilAssertion(t, "results", "show", "--scan-id", "MOCK_NO_VULNERABILITIES", "--report-format", "gl-sast")

func TestRunGetResultsByScanIdGLSastAndAScaFormat(t *testing.T) {
execCmdNilAssertion(t, "results", "show", "--scan-id", "MOCK", "--report-format", "gl-sast,gl-sca")
// Run test for gl-sast report type
// Check if the file exists and vulnerabilities is empty, then delete the file
if _, err := os.Stat(fmt.Sprintf("%s.%s-report.json", fileName, printer.FormatGL)); err == nil {
t.Logf("File exists: %s.%s", fileName, printer.FormatGL)
resultsData, err := os.ReadFile(fmt.Sprintf("%s.%s-report.json", fileName, printer.FormatGL))
if err != nil {
t.Logf("Failed to read file: %v", err)
}
os.Remove(fmt.Sprintf("%s.%s", fileName, printer.FormatGLSast))
os.Remove(fmt.Sprintf("%s.%s", fileName, printer.FormatGLSca))
}

var results wrappers.GlSastResultsCollection
if err := json.Unmarshal(resultsData, &results); err != nil {
t.Logf("Failed to unmarshal JSON: %v", err)
}
assert.Equal(t, len(results.Vulnerabilities), 0, "No vulnerabilities should be found")
if err := os.Remove(fmt.Sprintf("%s.%s-report.json", fileName, printer.FormatGL)); err != nil {
t.Logf("Failed to delete file: %v", err)
}
t.Log("File deleted successfully.")
}
func TestRunGetResultsByScanIdGLScaFormat(t *testing.T) {
execCmdNilAssertion(t, "results", "show", "--scan-id", "MOCK", "--report-format", "gl-sca")
// Run test for gl-sca report type
os.Remove(fmt.Sprintf("%s.%s", fileName, printer.FormatGLSca))
}

func Test_addPackageInformation(t *testing.T) {
Expand Down Expand Up @@ -593,6 +580,56 @@ func Test_addPackageInformation(t *testing.T) {
assert.Equal(t, expectedFixLink, actualFixLink, "FixLink should match the result ID")
}

func TestRunGetResultsByScanIdGLSastFormat_NoVulnerabilities_Success(t *testing.T) {
// Execute the command and perform nil assertion
execCmdNilAssertion(t, "results", "show", "--scan-id", "MOCK_NO_VULNERABILITIES", "--report-format", "gl-sast")

// Run test for gl-sast report type
// Check if the file exists and vulnerabilities is empty, then delete the file
if _, err := os.Stat(fmt.Sprintf("%s.%s-report.json", fileName, printer.FormatGLSast)); err == nil {
t.Logf("File exists: %s.%s", fileName, printer.FormatGLSast)
resultsData, err := os.ReadFile(fmt.Sprintf("%s.%s-report.json", fileName, printer.FormatGLSast))
if err != nil {
t.Logf("Failed to read file: %v", err)
}

var results wrappers.GlSastResultsCollection
if err := json.Unmarshal(resultsData, &results); err != nil {
t.Logf("Failed to unmarshal JSON: %v", err)
}
assert.Equal(t, len(results.Vulnerabilities), 0, "No vulnerabilities should be found")
if err := os.Remove(fmt.Sprintf("%s.%s-report.json", fileName, printer.FormatGLSast)); err != nil {
t.Logf("Failed to delete file: %v", err)
}
t.Log("File deleted successfully.")
}
}

func TestRunGetResultsByScanIdGLScaFormat_NoVulnerabilities_Success(t *testing.T) {
// Execute the command and perform nil assertion
execCmdNilAssertion(t, "results", "show", "--scan-id", "MOCK_NO_VULNERABILITIES", "--report-format", "gl-sca")

// Run test for gl-sca report type
// Check if the file exists and vulnerabilities is empty, then delete the file
if _, err := os.Stat(fmt.Sprintf("%s.%s-report.json", fileName, printer.FormatGLSca)); err == nil {
t.Logf("File exists: %s.%s", fileName, printer.FormatGLSca)
resultsData, err := os.ReadFile(fmt.Sprintf("%s.%s-report.json", fileName, printer.FormatGLSca))
if err != nil {
t.Logf("Failed to read file: %v", err)
}

var results wrappers.GlScaResultsCollection
if err := json.Unmarshal(resultsData, &results); err != nil {
t.Logf("Failed to unmarshal JSON: %v", err)
}
assert.Equal(t, len(results.Vulnerabilities), 0, "No vulnerabilities should be found")
if err := os.Remove(fmt.Sprintf("%s.%s-report.json", fileName, printer.FormatGLSca)); err != nil {
t.Logf("Failed to delete file: %v", err)
}
t.Log("File deleted successfully.")
}
}

func TestRunGetResultsByScanIdSummaryConsoleFormat_ScsNotScanned_ScsMissingInReport(t *testing.T) {
mock.HasScs = false
mock.ScsScanPartial = false
Expand Down
3 changes: 2 additions & 1 deletion internal/commands/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,8 @@ func scanCreateSubCommand(
printer.FormatSbom,
printer.FormatPDF,
printer.FormatSummaryMarkdown,
printer.FormatGL,
printer.FormatGLSast,
printer.FormatGLSca,
)
createScanCmd.PersistentFlags().String(commonParams.APIDocumentationFlag, "", apiDocumentationFlagDescription)
createScanCmd.PersistentFlags().String(commonParams.ExploitablePathFlag, "", exploitablePathFlagDescription)
Expand Down
3 changes: 2 additions & 1 deletion internal/commands/util/printer/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ const (
FormatSummaryMarkdown = "markdown"
FormatSbom = "sbom"
FormatXML = "xml"
FormatGL = "gl-sast"
FormatGLSast = "gl-sast"
FormatGLSca = "gl-sca"
)

func Print(w io.Writer, view interface{}, format string) error {
Expand Down
10 changes: 6 additions & 4 deletions internal/wrappers/results-gl-sast.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package wrappers

const (
AnalyzerName = "CxOne"
AnalyzerID = AnalyzerName + "-SAST"
AnalyzerURL = "https://checkmarx.com/"
VendorName = "Checkmarx"
AnalyzerName = "CxOne"
AnalyzerID = AnalyzerName + "-SAST"
AnalyzerURL = "https://checkmarx.com/"
VendorName = "Checkmarx"
SastSchema = "https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/sast-report-format.json"
SastSchemaVersion = "15.0"
)

type GlSastResultsCollection struct {
Expand Down
Loading

0 comments on commit 711edd2

Please sign in to comment.