diff --git a/internal/commands/result.go b/internal/commands/result.go index c4ddaa7aa..8c8bb9b0c 100644 --- a/internal/commands/result.go +++ b/internal/commands/result.go @@ -38,7 +38,8 @@ const ( lowLabel = "low" infoLabel = "info" sonarTypeLabel = "_sonar" - glSastTypeLobel = ".gl-sast-report" + glSastTypeLabel = ".gl-sast-report" + glDependencyTypeLabel = ".gl-dependency-report" directoryPermission = 0700 infoSonar = "INFO" lowSonar = "MINOR" @@ -103,6 +104,7 @@ var summaryFormats = []string{ printer.FormatSummaryMarkdown, printer.FormatSbom, printer.FormatGL, + printer.FormatGLDependency, } var filterResultsListFlagUsage = fmt.Sprintf( @@ -200,6 +202,7 @@ func resultShowSubCommand( printer.FormatPDF, printer.FormatSummaryMarkdown, printer.FormatGL, + printer.FormatGLDependency, ) resultShowCmd.PersistentFlags().String(commonParams.ReportFormatPdfToEmailFlag, "", pdfToEmailFlagDescription) resultShowCmd.PersistentFlags().String(commonParams.ReportSbomFormatFlag, defaultSbomOption, sbomReportFlagDescription) @@ -914,9 +917,14 @@ func createReport(format, return exportJSONResults(jsonRpt, results) } if printer.IsFormat(format, printer.FormatGL) { - jsonRpt := createTargetName(fmt.Sprintf("%s%s", targetFile, glSastTypeLobel), targetPath, printer.FormatJSON) + jsonRpt := createTargetName(fmt.Sprintf("%s%s", targetFile, glSastTypeLabel), targetPath, printer.FormatJSON) return exportGlSastResults(jsonRpt, results, summary) } + if printer.IsFormat(format, printer.FormatGLDependency) { + jsonRpt := createTargetName(fmt.Sprintf("%s%s", targetFile, glDependencyTypeLabel), targetPath, printer.FormatJSON) + return exportGlDependencyResults(jsonRpt, results, summary) + } + if printer.IsFormat(format, printer.FormatSummaryConsole) { return writeConsoleSummary(summary) } @@ -1080,6 +1088,53 @@ func exportGlSastResults(targetFile string, results *wrappers.ScanResultsCollect defer f.Close() return nil } + +func exportGlDependencyResults(targetFile string, results *wrappers.ScanResultsCollection, summary *wrappers.ResultSummary) error { + log.Println("Creating Gl-dependency: ", targetFile) + var glDependencyResult = new(wrappers.GlDependencyResultsCollection) + err := addScanToGlDependencyReport(summary, glDependencyResult) + convertCxResultToGlDependencyVulnerability(results, glDependencyResult, summary.BaseURI) + convertCxResultToGlDependencyFiles(results, glDependencyResult, summary.BaseURI) + resultsJSON, err := json.Marshal(glDependencyResult) + if err != nil { + 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) + } + _, _ = fmt.Fprintln(f, string(resultsJSON)) + defer f.Close() + + return nil +} + +func addScanToGlDependencyReport(summary *wrappers.ResultSummary, glDependencyResult *wrappers.GlDependencyResultsCollection) error { + createdAt, err := time.Parse(summaryCreatedAtLayout, summary.CreatedAt) + if err != nil { + return err + } + + glDependencyResult.Schema = "https://gitlab.com/gitlab-org/gitlab/-/raw/master/lib/gitlab/ci/parsers/security/validators/schemas/15.0.0/sast-report-format.json" + glDependencyResult.Version = "15.0.0" + glDependencyResult.Scan.Analyzer.VendorGlSCA.VendorGlname = wrappers.AnalyzerScaID + glDependencyResult.Scan.Analyzer.Name = wrappers.AnalyzerScaID + glDependencyResult.Scan.Analyzer.Name = wrappers.AnalyzerScaID + glDependencyResult.Scan.Analyzer.Id = wrappers.ScannerId + glDependencyResult.Scan.Scanner.Id = wrappers.ScannerId + glDependencyResult.Scan.Scanner.Name = wrappers.AnalyzerScaID + glDependencyResult.Scan.Scanner.VendorGlSCA.VendorGlname = wrappers.AnalyzerScaID + glDependencyResult.Scan.Status = commonParams.Success + glDependencyResult.Scan.Type = wrappers.ScannerType + glDependencyResult.Scan.StartTime = createdAt.Format(glTimeFormat) + glDependencyResult.Scan.EndTime = createdAt.Format(glTimeFormat) + glDependencyResult.Scan.Scanner.Name = wrappers.AnalyzerScaID + glDependencyResult.Scan.Scanner.VersionGlSca = commonParams.Version + glDependencyResult.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 { @@ -1374,6 +1429,21 @@ func convertCxResultToGlVulnerability(results *wrappers.ScanResultsCollection, g } } +func convertCxResultToGlDependencyVulnerability(results *wrappers.ScanResultsCollection, glDependencyResult *wrappers.GlDependencyResultsCollection, summaryBaseURI string) { + for _, result := range results.Results { + if strings.TrimSpace(result.Type) == commonParams.ScaType { + glDependencyResult = parseGlDependencyVulnerability(result, glDependencyResult) + } + } +} + +func convertCxResultToGlDependencyFiles(results *wrappers.ScanResultsCollection, glDependencyResult *wrappers.GlDependencyResultsCollection, summaryBaseURI string) { + for _, result := range results.Results { + if strings.TrimSpace(result.Type) == commonParams.ScaType { + glDependencyResult = parseGlDependencyFiles(result, glDependencyResult) + } + } +} func parseGlSastVulnerability(result *wrappers.ScanResult, glSast *wrappers.GlSastResultsCollection, summaryBaseURI string) *wrappers.GlSastResultsCollection { queryName := result.ScanResultData.QueryName fileName := result.ScanResultData.Nodes[0].FileName @@ -1429,6 +1499,107 @@ func parseGlSastVulnerability(result *wrappers.ScanResult, glSast *wrappers.GlSa return glSast } +func parseGlDependencyVulnerability(result *wrappers.ScanResult, glDependencyResult *wrappers.GlDependencyResultsCollection) *wrappers.GlDependencyResultsCollection { + + if result.ScanResultData.ScaPackageCollection != nil { + + glDependencyResult.Vulnerabilities = append(glDependencyResult.Vulnerabilities, wrappers.GlDepVulnerabilities{ + Id: result.ID, + Name: result.VulnerabilityDetails.CveName, + Description: result.Description, + Severity: cases.Title(language.English).String(result.Severity), + Solution: result.ScanResultData.RecommendedVersion, + Identifiers: collectDependencyPackageData(result), + Links: collectDependencyPackageLinks(result), + TrackingDep: wrappers.TrackingDep{ + Items: collectDependencyPackageItemsDep(result), + }, + Flags: make([]string, 0), + LocationDep: wrappers.GlDepVulnerabilityLocation{ + File: *(result.ScanResultData.ScaPackageCollection.Locations[0]), + Dependency: wrappers.DependencyLocation{ + Package: wrappers.PackageName{Name: result.ScanResultData.PackageIdentifier}, + DependencyLocationVersion: "", + Iid: "", + Direct: result.ScanResultData.ScaPackageCollection.IsDirectDependency, + DependencyPath: "", + }, + }, + }) + } + return glDependencyResult +} + +func parseGlDependencyFiles(result *wrappers.ScanResult, glDependencyResult *wrappers.GlDependencyResultsCollection) *wrappers.GlDependencyResultsCollection { + if result.ScanResultData.ScaPackageCollection != nil { + glDependencyResult.DependencyFiles = append(glDependencyResult.DependencyFiles, wrappers.DependencyFile{ + Path: result.ScanResultData.ScaPackageCollection.ID, ///Need to enter correct File path + PackageManager: result.ScanResultData.ScaPackageCollection.ID, //Need to enter correct Package_manager + Dependencies: collectDependencyFileLocations(result), + }) + } + return glDependencyResult +} + +func collectDependencyFileLocations(result *wrappers.ScanResult) []wrappers.DependencyLocation { + allIdentifierLocations := []wrappers.DependencyLocation{} + for i := 0; i < len(result.ScanResultData.PackageData); i++ { + allIdentifierLocations = append(allIdentifierLocations, wrappers.DependencyLocation{ + Package: wrappers.PackageName{ + Name: result.ScanResultData.PackageData[i].Type, + }, + DependencyLocationVersion: result.ScanResultData.PackageData[i].URL, + Iid: result.ScanResultData.PackageData[i].Type, + Direct: true, + DependencyPath: result.ScanResultData.PackageData[i].Type, + }) + } + return allIdentifierLocations +} + +func collectDependencyPackageItemsDep(result *wrappers.ScanResult) []wrappers.ItemDep { + allDependencyPackageItemDep := []wrappers.ItemDep{} + if result.ScanResultData.ScaPackageCollection != nil { + allDependencyPackageItemDep = append(allDependencyPackageItemDep, wrappers.ItemDep{ + Signature: []wrappers.SignatureDep{{Algorithm: "SCA-Algorithm ", Value: "NA"}}, + File: *result.ScanResultData.ScaPackageCollection.DependencyPathArray[0][0].Locations[0], + EndLine: 0, + StartLine: 0, + }) + } + return allDependencyPackageItemDep + +} + +func collectDependencyPackageLinks(result *wrappers.ScanResult) []wrappers.LinkDep { + allDependencyPackageLinks := []wrappers.LinkDep{} + for i := 0; i < len(result.ScanResultData.PackageData); i++ { + + allDependencyPackageLinks = append(allDependencyPackageLinks, wrappers.LinkDep{ + Name: result.ScanResultData.PackageData[i].Type, + Url: result.ScanResultData.PackageData[i].URL, + }) + + } + return allDependencyPackageLinks + +} + +func collectDependencyPackageData(result *wrappers.ScanResult) []wrappers.IdentifierDep { + allIdentifierDep := []wrappers.IdentifierDep{} + for i := 0; i < len(result.ScanResultData.PackageData); i++ { + + allIdentifierDep = append(allIdentifierDep, wrappers.IdentifierDep{ + Type: result.ScanResultData.PackageData[i].Type, + Value: result.ScanResultData.PackageData[i].URL, + Name: result.ScanResultData.PackageData[i].URL, + }) + + } + return allIdentifierDep +} + +// func IdentifiersPackageData func convertCxResultsToSonar(results *wrappers.ScanResultsCollection) *wrappers.ScanResultsSonar { var sonar = new(wrappers.ScanResultsSonar) sonar.Results = parseResultsSonar(results) diff --git a/internal/commands/util/printer/printer.go b/internal/commands/util/printer/printer.go index f6c83a5f7..5d1efeb74 100644 --- a/internal/commands/util/printer/printer.go +++ b/internal/commands/util/printer/printer.go @@ -28,6 +28,7 @@ const ( FormatSbom = "sbom" FormatXML = "xml" FormatGL = "gl-sast" + FormatGLDependency = "gl-dependency" ) func Print(w io.Writer, view interface{}, format string) error { diff --git a/internal/wrappers/results-gl-dependency.go b/internal/wrappers/results-gl-dependency.go new file mode 100644 index 000000000..861178af0 --- /dev/null +++ b/internal/wrappers/results-gl-dependency.go @@ -0,0 +1,106 @@ +package wrappers + +const ( + AnalyzerScaName = "CxOne" + AnalyzerScaID = AnalyzerScaName + "-SCA" + ScannerId = "SCA" + ScannerType = "dependency_scanning" +) + +type GlDependencyResultsCollection struct { + Scan ScanGlDepReport `json:"scan"` + Schema string `json:"schema"` + Version string `json:"version"` + Vulnerabilities []GlDepVulnerabilities `json:"vulnerabilities"` + DependencyFiles []DependencyFile `json:"dependency_files"` +} + +type GlVendor struct { + VendorGlname string `json:"name"` +} + +type ScanGlDepReport struct { + EndTime string `json:"end_time,omitempty"` + Analyzer GlDepAnalyzer `json:"analyzer,omitempty"` + Scanner GlDepScanner `json:"scanner,omitempty"` + StartTime string `json:"start_time,omitempty"` + Status string `json:"status,omitempty"` + Type string `json:"type"` +} + +type GlDepAnalyzer struct { + Id string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + VendorGlSCA GlVendor `json:"vendor"` + VersionGlSca string `json:"version,omitempty"` +} + +type GlDepScanner struct { + Id string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + VersionGlSca string `json:"version,omitempty"` + VendorGlSCA GlVendor `json:"vendor"` +} + +type GlDepVulnerabilities struct { + Id string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Severity string `json:"severity"` + Solution interface{} `json:"solution"` + Identifiers []IdentifierDep `json:"identifiers"` + Links []LinkDep `json:"links"` + TrackingDep TrackingDep `json:"tracking"` + Flags []string `json:"flags"` + LocationDep GlDepVulnerabilityLocation `json:"location"` +} + +type IdentifierDep struct { + Type string `json:"type"` + Name string `json:"name"` + Value string `json:"value"` +} + +type LinkDep struct { + Name string `json:"name,omitempty"` + Url string `json:"url,omitempty"` +} + +type TrackingDep struct { + Items []ItemDep `json:"items"` +} + +type ItemDep struct { + Signature []SignatureDep `json:"signatures"` + File string `json:"file"` + EndLine uint `json:"end_line"` + StartLine uint `json:"start_line"` +} + +type SignatureDep struct { + Algorithm string `json:"algorithm"` + Value string `json:"value"` +} + +type GlDepVulnerabilityLocation struct { + File string `json:"file"` + Dependency DependencyLocation `json:"dependency"` +} + +type DependencyLocation struct { + Package PackageName `json:"package"` + DependencyLocationVersion string `json:"version"` + Iid string `json:"iid"` + Direct bool `json:"direct"` + DependencyPath string `json:"iid"` +} + +type PackageName struct { + Name string `json:"name"` +} + +type DependencyFile struct { + Path string `json:"path"` + PackageManager string `json:"package_manager"` + Dependencies []DependencyLocation `json:"dependencies"` +}