From 64585a6a9685f71f6892269f16ed4afbc479e2a9 Mon Sep 17 00:00:00 2001
From: Assaf Attias <49212512+attiasas@users.noreply.github.com>
Date: Wed, 4 Sep 2024 17:57:01 +0300
Subject: [PATCH] Record SARIF results after security commands to upload for
GitHub (#138)
---
commands/audit/audit.go | 3 +-
commands/curation/curationaudit.go | 15 +-
commands/curation/curationaudit_test.go | 9 +-
commands/enrich/enrich.go | 2 +-
commands/scan/buildscan.go | 3 +-
commands/scan/dockerscan.go | 11 +-
commands/scan/scan.go | 14 +-
formats/sarifutils/sarifutils.go | 177 ++++++-
formats/sarifutils/sarifutils_test.go | 2 +-
formats/sarifutils/test_sarifutils.go | 83 +++-
go.mod | 2 +-
go.sum | 4 +-
.../other/jobSummary/security_section.md | 7 +-
.../violations_not_extended_view.md | 2 +-
utils/paths.go | 11 +-
utils/results.go | 6 +-
utils/resultstable.go | 18 +-
utils/resultstable_test.go | 2 +-
utils/resultwriter.go | 438 ++++++++++++++++--
utils/resultwriter_test.go | 274 ++++++++++-
utils/securityJobSummary.go | 158 +++++--
utils/securityJobSummary_test.go | 8 +-
utils/utils.go | 36 ++
23 files changed, 1116 insertions(+), 169 deletions(-)
diff --git a/commands/audit/audit.go b/commands/audit/audit.go
index 76b56c89..016ff212 100644
--- a/commands/audit/audit.go
+++ b/commands/audit/audit.go
@@ -140,7 +140,6 @@ func (auditCmd *AuditCommand) Run() (err error) {
SetOutputFormat(auditCmd.OutputFormat()).
SetPrintExtendedTable(auditCmd.PrintExtendedTable).
SetExtraMessages(messages).
- SetScanType(services.Dependency).
SetSubScansPreformed(auditCmd.ScansToPerform()).
PrintScanResults(); err != nil {
return
@@ -170,7 +169,7 @@ func (auditCmd *AuditCommand) HasViolationContext() bool {
// If the current server is entitled for JAS, the advanced security results will be included in the scan results.
func RunAudit(auditParams *AuditParams) (results *utils.Results, err error) {
// Initialize Results struct
- results = utils.NewAuditResults()
+ results = utils.NewAuditResults(utils.SourceCode)
serverDetails, err := auditParams.ServerDetails()
if err != nil {
return
diff --git a/commands/curation/curationaudit.go b/commands/curation/curationaudit.go
index 65b988a6..dd63b6c8 100644
--- a/commands/curation/curationaudit.go
+++ b/commands/curation/curationaudit.go
@@ -4,6 +4,14 @@ import (
"encoding/json"
"errors"
"fmt"
+ "net/http"
+ "os"
+ "path/filepath"
+ "regexp"
+ "sort"
+ "strings"
+ "sync"
+
"golang.org/x/exp/maps"
"github.com/jfrog/gofrog/datastructures"
@@ -28,13 +36,6 @@ import (
"github.com/jfrog/jfrog-client-go/utils/log"
xrayClient "github.com/jfrog/jfrog-client-go/xray"
xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils"
- "net/http"
- "os"
- "path/filepath"
- "regexp"
- "sort"
- "strings"
- "sync"
)
const (
diff --git a/commands/curation/curationaudit_test.go b/commands/curation/curationaudit_test.go
index a61a5b84..05f29595 100644
--- a/commands/curation/curationaudit_test.go
+++ b/commands/curation/curationaudit_test.go
@@ -979,7 +979,14 @@ func Test_convertResultsToSummary(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- assert.ElementsMatch(t, tt.expected.Scans, convertResultsToSummary(tt.input).Scans)
+ summary := convertResultsToSummary(tt.input)
+ // Sort Blocked base on count (low first) to make the test deterministic
+ for _, scan := range summary.Scans {
+ sort.Slice(scan.CuratedPackages.Blocked, func(i, j int) bool {
+ return len(scan.CuratedPackages.Blocked[i].Packages) < len(scan.CuratedPackages.Blocked[j].Packages)
+ })
+ }
+ assert.Equal(t, tt.expected, summary)
})
}
}
diff --git a/commands/enrich/enrich.go b/commands/enrich/enrich.go
index a2137cc2..badbe2df 100644
--- a/commands/enrich/enrich.go
+++ b/commands/enrich/enrich.go
@@ -190,7 +190,7 @@ func (enrichCmd *EnrichCommand) Run() (err error) {
scanErrors = appendErrorSlice(scanErrors, fileProducerErrors)
scanErrors = appendErrorSlice(scanErrors, indexedFileProducerErrors)
- scanResults := xrutils.NewAuditResults()
+ scanResults := xrutils.NewAuditResults(utils.SBOM)
scanResults.XrayVersion = xrayVersion
scanResults.ScaResults = flatResults
diff --git a/commands/scan/buildscan.go b/commands/scan/buildscan.go
index f85cf2d0..a1a59148 100644
--- a/commands/scan/buildscan.go
+++ b/commands/scan/buildscan.go
@@ -149,7 +149,7 @@ func (bsc *BuildScanCommand) runBuildScanAndPrintResults(xrayManager *xray.XrayS
XrayDataUrl: buildScanResults.MoreDetailsUrl,
}}
- scanResults := utils.NewAuditResults()
+ scanResults := utils.NewAuditResults(utils.Build)
scanResults.XrayVersion = xrayVersion
scanResults.ScaResults = []*utils.ScaScanResult{{Target: fmt.Sprintf("%s (%s)", params.BuildName, params.BuildNumber), XrayResults: scanResponse}}
@@ -160,7 +160,6 @@ func (bsc *BuildScanCommand) runBuildScanAndPrintResults(xrayManager *xray.XrayS
SetIncludeLicenses(false).
SetIsMultipleRootProject(true).
SetPrintExtendedTable(bsc.printExtendedTable).
- SetScanType(services.Binary).
SetExtraMessages(nil)
if bsc.outputFormat != outputFormat.Table {
diff --git a/commands/scan/dockerscan.go b/commands/scan/dockerscan.go
index 7d260f24..dfb9c59c 100644
--- a/commands/scan/dockerscan.go
+++ b/commands/scan/dockerscan.go
@@ -96,12 +96,19 @@ func (dsc *DockerScanCommand) Run() (err error) {
err = errorutils.CheckError(e)
}
}()
- return dsc.ScanCommand.RunAndRecordResults(func(scanResults *utils.Results) (err error) {
+ return dsc.ScanCommand.RunAndRecordResults(utils.DockerImage, func(scanResults *utils.Results) (err error) {
if scanResults == nil {
return
}
+ if scanResults.ScaResults != nil {
+ for _, result := range scanResults.ScaResults {
+ result.Name = dsc.imageTag
+ }
+ }
dsc.analyticsMetricsService.UpdateGeneralEvent(dsc.analyticsMetricsService.CreateXscAnalyticsGeneralEventFinalizeFromAuditResults(scanResults))
-
+ if err = utils.RecordSarifOutput(scanResults); err != nil {
+ return
+ }
return utils.RecordSecurityCommandSummary(utils.NewDockerScanSummary(
scanResults,
dsc.ScanCommand.serverDetails,
diff --git a/commands/scan/scan.go b/commands/scan/scan.go
index 37bba0c0..a3d913f5 100644
--- a/commands/scan/scan.go
+++ b/commands/scan/scan.go
@@ -194,7 +194,10 @@ func (scanCmd *ScanCommand) indexFile(filePath string) (*xrayUtils.BinaryGraphNo
}
func (scanCmd *ScanCommand) Run() (err error) {
- return scanCmd.RunAndRecordResults(func(scanResults *utils.Results) error {
+ return scanCmd.RunAndRecordResults(utils.Binary, func(scanResults *utils.Results) (err error) {
+ if err = utils.RecordSarifOutput(scanResults); err != nil {
+ return
+ }
return utils.RecordSecurityCommandSummary(utils.NewBinaryScanSummary(
scanResults,
scanCmd.serverDetails,
@@ -204,7 +207,7 @@ func (scanCmd *ScanCommand) Run() (err error) {
})
}
-func (scanCmd *ScanCommand) RunAndRecordResults(recordResFunc func(scanResults *utils.Results) error) (err error) {
+func (scanCmd *ScanCommand) RunAndRecordResults(cmdType utils.CommandType, recordResFunc func(scanResults *utils.Results) error) (err error) {
defer func() {
if err != nil {
var e *exec.ExitError
@@ -220,7 +223,7 @@ func (scanCmd *ScanCommand) RunAndRecordResults(recordResFunc func(scanResults *
return err
}
- scanResults := utils.NewAuditResults()
+ scanResults := utils.NewAuditResults(cmdType)
scanResults.XrayVersion = xrayVersion
if scanCmd.analyticsMetricsService != nil {
scanResults.MultiScanId = scanCmd.analyticsMetricsService.GetMsi()
@@ -323,15 +326,10 @@ func (scanCmd *ScanCommand) RunAndRecordResults(recordResFunc func(scanResults *
SetIncludeLicenses(scanCmd.includeLicenses).
SetPrintExtendedTable(scanCmd.printExtendedTable).
SetIsMultipleRootProject(scanResults.IsMultipleProject()).
- SetScanType(services.Binary).
PrintScanResults(); err != nil {
return
}
- if err != nil {
- return err
- }
-
if err = recordResFunc(scanResults); err != nil {
return err
}
diff --git a/formats/sarifutils/sarifutils.go b/formats/sarifutils/sarifutils.go
index e061c4ff..6b183cb5 100644
--- a/formats/sarifutils/sarifutils.go
+++ b/formats/sarifutils/sarifutils.go
@@ -1,12 +1,10 @@
package sarifutils
import (
- "encoding/json"
"fmt"
"path/filepath"
"strings"
- "github.com/jfrog/jfrog-client-go/utils"
"github.com/jfrog/jfrog-client-go/utils/errorutils"
"github.com/owenrumney/go-sarif/v2/sarif"
)
@@ -19,12 +17,42 @@ func NewReport() (*sarif.Report, error) {
return report, nil
}
-func ConvertSarifReportToString(report *sarif.Report) (sarifStr string, err error) {
- out, err := json.Marshal(report)
- if err != nil {
- return "", errorutils.CheckError(err)
+func CombineReports(reports ...*sarif.Report) (combined *sarif.Report, err error) {
+ if combined, err = NewReport(); err != nil {
+ return
+ }
+ for _, report := range reports {
+ for _, run := range report.Runs {
+ combined.AddRun(run)
+ }
+ }
+ return
+}
+
+func NewPhysicalLocation(physicalPath string) *sarif.PhysicalLocation {
+ return &sarif.PhysicalLocation{
+ ArtifactLocation: &sarif.ArtifactLocation{
+ URI: &physicalPath,
+ },
+ }
+}
+
+func NewPhysicalLocationWithRegion(physicalPath string, startRow, endRow, startCol, endCol int) *sarif.PhysicalLocation {
+ location := NewPhysicalLocation(physicalPath)
+ location.Region = &sarif.Region{
+ StartLine: &startRow,
+ EndLine: &endRow,
+ StartColumn: &startCol,
+ EndColumn: &endCol,
+ }
+ return location
+}
+
+func NewLogicalLocation(name, kind string) *sarif.LogicalLocation {
+ return &sarif.LogicalLocation{
+ Name: &name,
+ Kind: &kind,
}
- return utils.IndentJson(out), nil
}
func ReadScanRunsFromFile(fileName string) (sarifRuns []*sarif.Run, err error) {
@@ -79,6 +107,19 @@ func isSameLocation(location *sarif.Location, other *sarif.Location) bool {
return GetLocationId(location) == GetLocationId(other)
}
+func GetLogicalLocation(kind string, location *sarif.Location) *sarif.LogicalLocation {
+ if location == nil {
+ return nil
+ }
+ // Search for a logical location that has the same kind as the location
+ for _, logicalLocation := range location.LogicalLocations {
+ if logicalLocation.Kind != nil && *logicalLocation.Kind == kind {
+ return logicalLocation
+ }
+ }
+ return nil
+}
+
func GetLocationId(location *sarif.Location) string {
return fmt.Sprintf("%s:%s:%d:%d:%d:%d",
GetLocationFileName(location),
@@ -90,6 +131,55 @@ func GetLocationId(location *sarif.Location) string {
)
}
+func SetRunToolName(toolName string, run *sarif.Run) {
+ if run.Tool.Driver == nil {
+ run.Tool.Driver = &sarif.ToolComponent{}
+ }
+ run.Tool.Driver.Name = toolName
+}
+
+func SetRunToolFullDescriptionText(txt string, run *sarif.Run) {
+ if run.Tool.Driver == nil {
+ run.Tool.Driver = &sarif.ToolComponent{}
+ }
+ if run.Tool.Driver.FullDescription == nil {
+ run.Tool.Driver.FullDescription = sarif.NewMultiformatMessageString(txt)
+ return
+ }
+ run.Tool.Driver.FullDescription.Text = &txt
+}
+
+func SetRunToolFullDescriptionMarkdown(markdown string, run *sarif.Run) {
+ if run.Tool.Driver == nil {
+ run.Tool.Driver = &sarif.ToolComponent{}
+ }
+ if run.Tool.Driver.FullDescription == nil {
+ run.Tool.Driver.FullDescription = sarif.NewMarkdownMultiformatMessageString(markdown)
+ }
+ run.Tool.Driver.FullDescription.Markdown = &markdown
+}
+
+func GetRunToolFullDescriptionText(run *sarif.Run) string {
+ if run.Tool.Driver != nil && run.Tool.Driver.FullDescription != nil && run.Tool.Driver.FullDescription.Text != nil {
+ return *run.Tool.Driver.FullDescription.Text
+ }
+ return ""
+}
+
+func GetRunToolFullDescriptionMarkdown(run *sarif.Run) string {
+ if run.Tool.Driver != nil && run.Tool.Driver.FullDescription != nil && run.Tool.Driver.FullDescription.Markdown != nil {
+ return *run.Tool.Driver.FullDescription.Markdown
+ }
+ return ""
+}
+
+func GetRunToolName(run *sarif.Run) string {
+ if run.Tool.Driver != nil {
+ return run.Tool.Driver.Name
+ }
+ return ""
+}
+
func GetResultsLocationCount(runs ...*sarif.Run) (count int) {
for _, run := range runs {
for _, result := range run.Results {
@@ -110,7 +200,10 @@ func GetRunsByWorkingDirectory(workingDirectory string, runs ...*sarif.Run) (fil
}
}
return
+}
+func SetResultMsgMarkdown(markdown string, result *sarif.Result) {
+ result.Message.Markdown = &markdown
}
func GetResultMsgText(result *sarif.Result) string {
@@ -127,6 +220,34 @@ func GetResultLevel(result *sarif.Result) string {
return ""
}
+func GetResultRuleId(result *sarif.Result) string {
+ if result.RuleID != nil {
+ return *result.RuleID
+ }
+ return ""
+}
+
+func IsFingerprintsExists(result *sarif.Result) bool {
+ return len(result.Fingerprints) > 0
+}
+
+func SetResultFingerprint(algorithm, value string, result *sarif.Result) {
+ if result.Fingerprints == nil {
+ result.Fingerprints = make(map[string]interface{})
+ }
+ result.Fingerprints[algorithm] = value
+}
+
+func GetResultLocationSnippets(result *sarif.Result) []string {
+ var snippets []string
+ for _, location := range result.Locations {
+ if snippet := GetLocationSnippet(location); snippet != "" {
+ snippets = append(snippets, snippet)
+ }
+ }
+ return snippets
+}
+
func GetLocationSnippet(location *sarif.Location) string {
region := getLocationRegion(location)
if region != nil && region.Snippet != nil {
@@ -148,6 +269,31 @@ func GetLocationFileName(location *sarif.Location) string {
return ""
}
+func GetResultFileLocations(result *sarif.Result) []string {
+ var locations []string
+ for _, location := range result.Locations {
+ locations = append(locations, GetLocationFileName(location))
+ }
+ return locations
+}
+
+func ConvertRunsPathsToRelative(runs ...*sarif.Run) {
+ for _, run := range runs {
+ for _, result := range run.Results {
+ for _, location := range result.Locations {
+ SetLocationFileName(location, GetRelativeLocationFileName(location, run.Invocations))
+ }
+ for _, flows := range result.CodeFlows {
+ for _, flow := range flows.ThreadFlows {
+ for _, location := range flow.Locations {
+ SetLocationFileName(location.Location, GetRelativeLocationFileName(location.Location, run.Invocations))
+ }
+ }
+ }
+ }
+ }
+}
+
func GetRelativeLocationFileName(location *sarif.Location, invocations []*sarif.Invocation) string {
wd := ""
if len(invocations) > 0 {
@@ -227,13 +373,28 @@ func IsResultKindNotPass(result *sarif.Result) bool {
return !(result.Kind != nil && *result.Kind == "pass")
}
-func GetRuleFullDescription(rule *sarif.ReportingDescriptor) string {
+func GetRuleFullDescriptionText(rule *sarif.ReportingDescriptor) string {
if rule.FullDescription != nil && rule.FullDescription.Text != nil {
return *rule.FullDescription.Text
}
return ""
}
+func SetRuleShortDescriptionText(value string, rule *sarif.ReportingDescriptor) {
+ if rule.ShortDescription == nil {
+ rule.ShortDescription = sarif.NewMultiformatMessageString(value)
+ return
+ }
+ rule.ShortDescription.Text = &value
+}
+
+func GetRuleShortDescriptionText(rule *sarif.ReportingDescriptor) string {
+ if rule.ShortDescription != nil && rule.ShortDescription.Text != nil {
+ return *rule.ShortDescription.Text
+ }
+ return ""
+}
+
func GetRunRules(run *sarif.Run) []*sarif.ReportingDescriptor {
if run != nil && run.Tool.Driver != nil {
return run.Tool.Driver.Rules
diff --git a/formats/sarifutils/sarifutils_test.go b/formats/sarifutils/sarifutils_test.go
index 302cdfd9..6363b515 100644
--- a/formats/sarifutils/sarifutils_test.go
+++ b/formats/sarifutils/sarifutils_test.go
@@ -544,7 +544,7 @@ func TestGetRuleFullDescription(t *testing.T) {
}
for _, test := range tests {
- assert.Equal(t, test.expectedOutput, GetRuleFullDescription(test.rule))
+ assert.Equal(t, test.expectedOutput, GetRuleFullDescriptionText(test.rule))
}
}
diff --git a/formats/sarifutils/test_sarifutils.go b/formats/sarifutils/test_sarifutils.go
index 2de6c19e..6848849a 100644
--- a/formats/sarifutils/test_sarifutils.go
+++ b/formats/sarifutils/test_sarifutils.go
@@ -2,8 +2,28 @@ package sarifutils
import "github.com/owenrumney/go-sarif/v2/sarif"
+func CreateRunWithDummyResultsInWd(wd string, results ...*sarif.Result) *sarif.Run {
+ return createRunWithDummyResults("", results...).WithInvocations([]*sarif.Invocation{sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation(wd))})
+}
+
func CreateRunWithDummyResults(results ...*sarif.Result) *sarif.Run {
- run := sarif.NewRunWithInformationURI("", "")
+ return createRunWithDummyResults("", results...)
+}
+
+func CreateDummyDriver(toolName, infoURI string, rules ...*sarif.ReportingDescriptor) *sarif.ToolComponent {
+ return &sarif.ToolComponent{
+ Name: toolName,
+ InformationURI: &infoURI,
+ Rules: rules,
+ }
+}
+
+func CreateRunNameWithResults(toolName string, results ...*sarif.Result) *sarif.Run {
+ return createRunWithDummyResults(toolName, results...)
+}
+
+func createRunWithDummyResults(toolName string, results ...*sarif.Result) *sarif.Run {
+ run := sarif.NewRunWithInformationURI(toolName, "")
for _, result := range results {
if result.RuleID != nil {
run.AddRule(*result.RuleID)
@@ -24,15 +44,60 @@ func CreateRunWithDummyResultAndRuleProperties(property, value string, result *s
return run
}
-func CreateResultWithLocations(msg, ruleId, level string, locations ...*sarif.Location) *sarif.Result {
+func CreateDummyResultInPath(fileName string) *sarif.Result {
+ return CreateResultWithOneLocation(fileName, 0, 0, 0, 0, "snippet", "rule", "level")
+}
+
+func CreateDummyResult(markdown, msg, ruleId, level string) *sarif.Result {
return &sarif.Result{
- Message: *sarif.NewTextMessage(msg),
- Locations: locations,
- Level: &level,
- RuleID: &ruleId,
+ Message: *sarif.NewTextMessage(msg).WithMarkdown(markdown),
+ Level: &level,
+ RuleID: &ruleId,
}
}
+func CreateResultWithDummyLocationAmdProperty(fileName, property, value string) *sarif.Result {
+ resultWithLocation := CreateDummyResultInPath(fileName)
+ resultWithLocation.Properties = map[string]interface{}{property: value}
+ return resultWithLocation
+}
+
+func CreateResultWithLocations(msg, ruleId, level string, locations ...*sarif.Location) *sarif.Result {
+ result := CreateDummyResult("", msg, ruleId, level)
+ result.Locations = locations
+ return result
+}
+
+func CreateDummyResultWithFingerprint(markdown, msg, algorithm, value string, locations ...*sarif.Location) *sarif.Result {
+ result := CreateDummyResult(markdown, msg, "rule", "level")
+ if result.RuleIndex == nil {
+ result.RuleIndex = newUintPtr(0)
+ }
+ result.Locations = locations
+ result.Fingerprints = map[string]interface{}{algorithm: value}
+ return result
+}
+
+func newUintPtr(v uint) *uint {
+ return &v
+}
+
+func CreateDummyResultWithPathAndLogicalLocation(fileName, logicalName, kind, property, value string) *sarif.Result {
+ result := CreateDummyResult("", "", "rule", "level")
+ result.Locations = append(result.Locations, CreateDummyLocationWithPathAndLogicalLocation(fileName, logicalName, kind, property, value))
+ return result
+}
+
+func CreateDummyLocationWithPathAndLogicalLocation(fileName, logicalName, kind, property, value string) *sarif.Location {
+ location := CreateDummyLocationInPath(fileName)
+ location.LogicalLocations = append(location.LogicalLocations, CreateLogicalLocationWithProperty(logicalName, kind, property, value))
+ return location
+}
+
+func CreateDummyLocationInPath(fileName string) *sarif.Location {
+ return CreateLocation(fileName, 0, 0, 0, 0, "snippet")
+}
+
func CreateLocation(fileName string, startLine, startCol, endLine, endCol int, snippet string) *sarif.Location {
return &sarif.Location{
PhysicalLocation: &sarif.PhysicalLocation{
@@ -46,6 +111,12 @@ func CreateLocation(fileName string, startLine, startCol, endLine, endCol int, s
}
}
+func CreateLogicalLocationWithProperty(name, kind, property, value string) *sarif.LogicalLocation {
+ location := sarif.NewLogicalLocation().WithName(name).WithKind(kind)
+ location.Properties = map[string]interface{}{property: value}
+ return location
+}
+
func CreateDummyPassingResult(ruleId string) *sarif.Result {
kind := "pass"
return &sarif.Result{
diff --git a/go.mod b/go.mod
index 42caa94e..ef59500e 100644
--- a/go.mod
+++ b/go.mod
@@ -10,7 +10,7 @@ require (
github.com/jfrog/froggit-go v1.16.1
github.com/jfrog/gofrog v1.7.5
github.com/jfrog/jfrog-apps-config v1.0.1
- github.com/jfrog/jfrog-cli-core/v2 v2.55.6
+ github.com/jfrog/jfrog-cli-core/v2 v2.55.7
github.com/jfrog/jfrog-client-go v1.46.1
github.com/magiconair/properties v1.8.7
github.com/owenrumney/go-sarif/v2 v2.3.0
diff --git a/go.sum b/go.sum
index 854d47fa..8ebca8bf 100644
--- a/go.sum
+++ b/go.sum
@@ -898,8 +898,8 @@ github.com/jfrog/gofrog v1.7.5 h1:dFgtEDefJdlq9cqTRoe09RLxS5Bxbe1Ev5+E6SmZHcg=
github.com/jfrog/gofrog v1.7.5/go.mod h1:jyGiCgiqSSR7k86hcUSu67XVvmvkkgWTmPsH25wI298=
github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYLipdsOFMY=
github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w=
-github.com/jfrog/jfrog-cli-core/v2 v2.55.6 h1:3tQuEdYgS2q7fkrrSG66OnO0S998FXGaY9BVsxSLst4=
-github.com/jfrog/jfrog-cli-core/v2 v2.55.6/go.mod h1:DPO5BfWAeOByahFMMy+PcjmbPlcyoRy7Bf2C5sGKVi0=
+github.com/jfrog/jfrog-cli-core/v2 v2.55.7 h1:V4dO2FMNIH49lov3dMj3jYRg8KBTG7hyhHI8ftYByf8=
+github.com/jfrog/jfrog-cli-core/v2 v2.55.7/go.mod h1:DPO5BfWAeOByahFMMy+PcjmbPlcyoRy7Bf2C5sGKVi0=
github.com/jfrog/jfrog-client-go v1.46.1 h1:ExqOF8ClOG9LO3vbm6jTIwQHHhprbu8lxB2RrM6mMI0=
github.com/jfrog/jfrog-client-go v1.46.1/go.mod h1:UCu2JNBfMp9rypEmCL84DCooG79xWIHVadZQR3Ab+BQ=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
diff --git a/tests/testdata/other/jobSummary/security_section.md b/tests/testdata/other/jobSummary/security_section.md
index 517122ca..685a59ce 100644
--- a/tests/testdata/other/jobSummary/security_section.md
+++ b/tests/testdata/other/jobSummary/security_section.md
@@ -1,8 +1,9 @@
-🔒 Security Summary
-#### Curation Audit
+🔒 Curation Audit
+
| Audit Summary | Project name | Audit Details |
|--------|--------|---------|
| | /application1 | Total Number of resolved packages: 6
🟢 Approved packages: 3
🔴 Blocked packages: 3Violated Policy: cvss_score, Condition: cvss score higher than 4.0 (2)
📦 npm://test:2.0.0
📦 npm://underscore:1.0.0Violated Policy: Malicious, Condition: Malicious package (1)
📦 npm://lodash:1.0.0
|
| | /application2 | Total Number of resolved packages: 3
|
-| | /application3 | Total Number of resolved packages: 5
🟢 Approved packages: 4
🔴 Blocked packages: 1Violated Policy: Aged, Condition: Package is aged (1)
📦 npm://test:1.0.0
|
\ No newline at end of file
+| | /application3 | Total Number of resolved packages: 5
🟢 Approved packages: 4
🔴 Blocked packages: 1Violated Policy: Aged, Condition: Package is aged (1)
📦 npm://test:1.0.0
|
+
\ No newline at end of file
diff --git a/tests/testdata/other/jobSummary/violations_not_extended_view.md b/tests/testdata/other/jobSummary/violations_not_extended_view.md
index 0702399a..1aead66d 100644
--- a/tests/testdata/other/jobSummary/violations_not_extended_view.md
+++ b/tests/testdata/other/jobSummary/violations_not_extended_view.md
@@ -1 +1 @@
-
watch: watch1
26 Policy Violations:	20 Security	2 Operational	1 License	3 Secrets
🐸 Unlock detailed findings
\ No newline at end of file
+watch: watch1
26 Policy Violations:	20 Security	2 Operational	1 License	3 Secrets
🐸 Unlock detailed findings
\ No newline at end of file
diff --git a/utils/paths.go b/utils/paths.go
index 918d1b9b..cafe460c 100644
--- a/utils/paths.go
+++ b/utils/paths.go
@@ -1,9 +1,6 @@
package utils
import (
- // #nosec G505 -- Not in use for secrets.
- "crypto/sha1"
- "encoding/hex"
"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
"github.com/jfrog/jfrog-cli-security/utils/techutils"
"os"
@@ -58,13 +55,7 @@ func getProjectPathHash() (string, error) {
if err != nil {
return "", err
}
- // #nosec G401 -- Not a secret hash.
- hasher := sha1.New()
- _, err = hasher.Write([]byte(workingDir))
- if err != nil {
- return "", err
- }
- return hex.EncodeToString(hasher.Sum(nil)), nil
+ return Sha1Hash(workingDir)
}
func GetCurationPipCacheFolder() (string, error) {
diff --git a/utils/results.go b/utils/results.go
index b1404099..bcee459b 100644
--- a/utils/results.go
+++ b/utils/results.go
@@ -10,6 +10,7 @@ import (
)
type Results struct {
+ ResultType CommandType
ScaResults []*ScaScanResult
XrayVersion string
ScansErr error
@@ -19,8 +20,8 @@ type Results struct {
MultiScanId string
}
-func NewAuditResults() *Results {
- return &Results{ExtendedScanResults: &ExtendedScanResults{}}
+func NewAuditResults(resultType CommandType) *Results {
+ return &Results{ResultType: resultType, ExtendedScanResults: &ExtendedScanResults{}}
}
func (r *Results) GetScaScansXrayResults() (results []services.ScanResponse) {
@@ -92,6 +93,7 @@ func (r *Results) CountScanResultsFindings(includeVulnerabilities, includeViolat
type ScaScanResult struct {
// Could be working directory (audit), file path (binary scan) or build name+number (build scan)
Target string `json:"Target"`
+ Name string `json:"Name,omitempty"`
Technology techutils.Technology `json:"Technology,omitempty"`
XrayResults []services.ScanResponse `json:"XrayResults,omitempty"`
Descriptors []string `json:"Descriptors,omitempty"`
diff --git a/utils/resultstable.go b/utils/resultstable.go
index bf87b948..06623eac 100644
--- a/utils/resultstable.go
+++ b/utils/resultstable.go
@@ -38,13 +38,13 @@ const (
// In case one (or more) of the violations contains the field FailBuild set to true, CliError with exit code 3 will be returned.
// Set printExtended to true to print fields with 'extended' tag.
// If the scan argument is set to true, print the scan tables.
-func PrintViolationsTable(violations []services.Violation, results *Results, multipleRoots, printExtended bool, scanType services.ScanType) error {
+func PrintViolationsTable(violations []services.Violation, results *Results, multipleRoots, printExtended bool) error {
securityViolationsRows, licenseViolationsRows, operationalRiskViolationsRows, err := prepareViolations(violations, results, multipleRoots, true, true)
if err != nil {
return err
}
// Print tables, if scan is true; print the scan tables.
- if scanType == services.Binary {
+ if results.ResultType.IsTargetBinary() {
err = coreutils.PrintTable(formats.ConvertToVulnerabilityScanTableRow(securityViolationsRows), "Security Violations", "No security violations were found", printExtended)
if err != nil {
return err
@@ -192,13 +192,13 @@ func prepareViolations(violations []services.Violation, results *Results, multip
// In case multipleRoots is true, the field Component will show the root of each impact path, otherwise it will show the root's child.
// Set printExtended to true to print fields with 'extended' tag.
// If the scan argument is set to true, print the scan tables.
-func PrintVulnerabilitiesTable(vulnerabilities []services.Vulnerability, results *Results, multipleRoots, printExtended bool, scanType services.ScanType) error {
+func PrintVulnerabilitiesTable(vulnerabilities []services.Vulnerability, results *Results, multipleRoots, printExtended bool, scanType CommandType) error {
vulnerabilitiesRows, err := prepareVulnerabilities(vulnerabilities, results, multipleRoots, true, true)
if err != nil {
return err
}
- if scanType == services.Binary {
+ if scanType.IsTargetBinary() {
return coreutils.PrintTable(formats.ConvertToVulnerabilityScanTableRow(vulnerabilitiesRows), "Vulnerable Components", "✨ No vulnerable components were found ✨", printExtended)
}
var emptyTableMessage string
@@ -300,12 +300,12 @@ func getJfrogResearchPriority(vulnerabilityOrViolation formats.VulnerabilityOrVi
// In case multipleRoots is true, the field Component will show the root of each impact path, otherwise it will show the root's child.
// Set printExtended to true to print fields with 'extended' tag.
// If the scan argument is set to true, print the scan tables.
-func PrintLicensesTable(licenses []services.License, printExtended bool, scanType services.ScanType) error {
+func PrintLicensesTable(licenses []services.License, printExtended bool, scanType CommandType) error {
licensesRows, err := PrepareLicenses(licenses)
if err != nil {
return err
}
- if scanType == services.Binary {
+ if scanType.IsTargetBinary() {
return coreutils.PrintTable(formats.ConvertToLicenseScanTableRow(licensesRows), "Licenses", "No licenses were found", printExtended)
}
return coreutils.PrintTable(formats.ConvertToLicenseTableRow(licensesRows), "Licenses", "No licenses were found", printExtended)
@@ -398,7 +398,7 @@ func prepareIacs(iacs []*sarif.Run, isTable bool) []formats.SourceCodeRow {
for _, iacResult := range iacRun.Results {
scannerDescription := ""
if rule, err := iacRun.GetRuleById(*iacResult.RuleID); err == nil {
- scannerDescription = sarifutils.GetRuleFullDescription(rule)
+ scannerDescription = sarifutils.GetRuleFullDescriptionText(rule)
}
currSeverity, err := severityutils.ParseSeverity(sarifutils.GetResultLevel(iacResult), true)
if err != nil {
@@ -452,7 +452,7 @@ func prepareSast(sasts []*sarif.Run, isTable bool) []formats.SourceCodeRow {
for _, sastResult := range sastRun.Results {
scannerDescription := ""
if rule, err := sastRun.GetRuleById(*sastResult.RuleID); err == nil {
- scannerDescription = sarifutils.GetRuleFullDescription(rule)
+ scannerDescription = sarifutils.GetRuleFullDescriptionText(rule)
}
currSeverity, err := severityutils.ParseSeverity(sarifutils.GetResultLevel(sastResult), true)
if err != nil {
@@ -935,7 +935,7 @@ func getCveApplicabilityField(cveId string, applicabilityScanResults []*sarif.Ru
var applicabilityStatuses []jasutils.ApplicabilityStatus
for _, applicabilityRun := range applicabilityScanResults {
if rule, _ := applicabilityRun.GetRuleById(jasutils.CveToApplicabilityRuleId(cveId)); rule != nil {
- applicability.ScannerDescription = sarifutils.GetRuleFullDescription(rule)
+ applicability.ScannerDescription = sarifutils.GetRuleFullDescriptionText(rule)
status := getApplicabilityStatusFromRule(rule)
if status != "" {
applicabilityStatuses = append(applicabilityStatuses, status)
diff --git a/utils/resultstable_test.go b/utils/resultstable_test.go
index ea41591c..d0e25ba2 100644
--- a/utils/resultstable_test.go
+++ b/utils/resultstable_test.go
@@ -27,7 +27,7 @@ func TestPrintViolationsTable(t *testing.T) {
}
for _, test := range tests {
- err := PrintViolationsTable(test.violations, NewAuditResults(), false, true, services.Binary)
+ err := PrintViolationsTable(test.violations, NewAuditResults(Binary), false, true)
assert.NoError(t, err)
if CheckIfFailBuild([]services.ScanResponse{{Violations: test.violations}}) {
err = NewFailBuildError()
diff --git a/utils/resultwriter.go b/utils/resultwriter.go
index cad4a751..331d7724 100644
--- a/utils/resultwriter.go
+++ b/utils/resultwriter.go
@@ -4,6 +4,9 @@ import (
"bytes"
"encoding/json"
"fmt"
+ "os"
+ "path/filepath"
+ "regexp"
"strconv"
"strings"
@@ -25,11 +28,24 @@ import (
)
const (
- BaseDocumentationURL = "https://docs.jfrog-applications.jfrog.io/jfrog-security-features/"
+ BaseDocumentationURL = "https://docs.jfrog-applications.jfrog.io/jfrog-security-features/"
+ CurrentWorkflowNameEnvVar = "GITHUB_WORKFLOW"
+ CurrentWorkflowRunNumberEnvVar = "GITHUB_RUN_NUMBER"
+ CurrentWorkflowWorkspaceEnvVar = "GITHUB_WORKSPACE"
+
+ MissingCveScore = "0"
+ maxPossibleCve = 10.0
+
+ // #nosec G101 -- Not credentials.
+ patchedBinarySecretScannerToolName = "JFrog Binary Secrets Scanner"
+ jfrogFingerprintAlgorithmName = "jfrogFingerprintHash"
)
-const MissingCveScore = "0"
-const maxPossibleCve = 10.0
+var (
+ GithubBaseWorkflowDir = filepath.Join(".github", "workflows")
+ dockerJasLocationPathPattern = regexp.MustCompile(`.*[\\/](?P[^\\/]+)[\\/](?P[0-9a-fA-F]+)[\\/](?P.*)`)
+ dockerScaComponentNamePattern = regexp.MustCompile(`(?P[^__]+)__(?P[0-9a-fA-F]+)\.tar`)
+)
type ResultsWriter struct {
// The scan results.
@@ -38,9 +54,9 @@ type ResultsWriter struct {
simpleJsonError []formats.SimpleJsonError
// Format The output format.
format format.OutputFormat
- // IncludeVulnerabilities If true, include all vulnerabilities as part of the output. Else, include violations only.
+ // IncludeVulnerabilities If true, include all vulnerabilities as part of the output.
includeVulnerabilities bool
- //
+ // If true, include violations as part of the output.
hasViolationContext bool
// IncludeLicenses If true, also include license violations as part of the output.
includeLicenses bool
@@ -48,8 +64,6 @@ type ResultsWriter struct {
isMultipleRoots bool
// PrintExtended, If true, show extended results.
printExtended bool
- // The scanType (binary,dependency)
- scanType services.ScanType
// For table format - show table only for the given subScansPreformed
subScansPreformed []SubScanType
// Messages - Option array of messages, to be displayed if the format is Table
@@ -77,11 +91,6 @@ func (rw *ResultsWriter) SetOutputFormat(f format.OutputFormat) *ResultsWriter {
return rw
}
-func (rw *ResultsWriter) SetScanType(scanType services.ScanType) *ResultsWriter {
- rw.scanType = scanType
- return rw
-}
-
func (rw *ResultsWriter) SetSimpleJsonError(jsonErrors []formats.SimpleJsonError) *ResultsWriter {
rw.simpleJsonError = jsonErrors
return rw
@@ -148,41 +157,41 @@ func (rw *ResultsWriter) printScanResultsTables() (err error) {
printMessage(coreutils.PrintTitle("The full scan results are available here: ") + coreutils.PrintLink(resultsPath))
}
log.Output()
- if shouldPrintTable(rw.subScansPreformed, ScaScan, rw.scanType) {
+ if shouldPrintTable(rw.subScansPreformed, ScaScan, rw.results.ResultType) {
if rw.hasViolationContext {
- if err = PrintViolationsTable(violations, rw.results, rw.isMultipleRoots, rw.printExtended, rw.scanType); err != nil {
+ if err = PrintViolationsTable(violations, rw.results, rw.isMultipleRoots, rw.printExtended); err != nil {
return
}
}
if rw.includeVulnerabilities {
- if err = PrintVulnerabilitiesTable(vulnerabilities, rw.results, rw.isMultipleRoots, rw.printExtended, rw.scanType); err != nil {
+ if err = PrintVulnerabilitiesTable(vulnerabilities, rw.results, rw.isMultipleRoots, rw.printExtended, rw.results.ResultType); err != nil {
return
}
}
if rw.includeLicenses {
- if err = PrintLicensesTable(licenses, rw.printExtended, rw.scanType); err != nil {
+ if err = PrintLicensesTable(licenses, rw.printExtended, rw.results.ResultType); err != nil {
return
}
}
}
- if shouldPrintTable(rw.subScansPreformed, SecretsScan, rw.scanType) {
+ if shouldPrintTable(rw.subScansPreformed, SecretsScan, rw.results.ResultType) {
if err = PrintSecretsTable(rw.results.ExtendedScanResults.SecretsScanResults, rw.results.ExtendedScanResults.EntitledForJas); err != nil {
return
}
}
- if shouldPrintTable(rw.subScansPreformed, IacScan, rw.scanType) {
+ if shouldPrintTable(rw.subScansPreformed, IacScan, rw.results.ResultType) {
if err = PrintIacTable(rw.results.ExtendedScanResults.IacScanResults, rw.results.ExtendedScanResults.EntitledForJas); err != nil {
return
}
}
- if !shouldPrintTable(rw.subScansPreformed, SastScan, rw.scanType) {
+ if !shouldPrintTable(rw.subScansPreformed, SastScan, rw.results.ResultType) {
return nil
}
return PrintSastTable(rw.results.ExtendedScanResults.SastScanResults, rw.results.ExtendedScanResults.EntitledForJas)
}
-func shouldPrintTable(requestedScans []SubScanType, subScan SubScanType, scanType services.ScanType) bool {
- if scanType == services.Binary && (subScan == IacScan || subScan == SastScan) {
+func shouldPrintTable(requestedScans []SubScanType, subScan SubScanType, scanType CommandType) bool {
+ if scanType.IsTargetBinary() && (subScan == IacScan || subScan == SastScan) {
return false
}
return len(requestedScans) == 0 || slices.Contains(requestedScans, subScan)
@@ -201,7 +210,7 @@ func printMessage(message string) {
log.Output("💬" + message)
}
-func GenereateSarifReportFromResults(results *Results, isMultipleRoots, includeLicenses bool, allowedLicenses []string) (report *sarif.Report, err error) {
+func GenerateSarifReportFromResults(results *Results, isMultipleRoots, includeLicenses bool, allowedLicenses []string) (report *sarif.Report, err error) {
report, err = sarifutils.NewReport()
if err != nil {
return
@@ -211,11 +220,10 @@ func GenereateSarifReportFromResults(results *Results, isMultipleRoots, includeL
return
}
- report.Runs = append(report.Runs, xrayRun)
- report.Runs = append(report.Runs, results.ExtendedScanResults.ApplicabilityScanResults...)
- report.Runs = append(report.Runs, results.ExtendedScanResults.IacScanResults...)
- report.Runs = append(report.Runs, results.ExtendedScanResults.SecretsScanResults...)
- report.Runs = append(report.Runs, results.ExtendedScanResults.SastScanResults...)
+ report.Runs = append(report.Runs, patchRunsToPassIngestionRules(ScaScan, results, xrayRun)...)
+ report.Runs = append(report.Runs, patchRunsToPassIngestionRules(IacScan, results, results.ExtendedScanResults.IacScanResults...)...)
+ report.Runs = append(report.Runs, patchRunsToPassIngestionRules(SecretsScan, results, results.ExtendedScanResults.SecretsScanResults...)...)
+ report.Runs = append(report.Runs, patchRunsToPassIngestionRules(SastScan, results, results.ExtendedScanResults.SastScanResults...)...)
return
}
@@ -225,10 +233,10 @@ func convertXrayResponsesToSarifRun(results *Results, isMultipleRoots, includeLi
if err != nil {
return
}
- xrayRun := sarif.NewRunWithInformationURI("JFrog Xray SCA", BaseDocumentationURL+"sca")
+ xrayRun := sarif.NewRunWithInformationURI("JFrog Xray Scanner", BaseDocumentationURL+"sca")
xrayRun.Tool.Driver.Version = &results.XrayVersion
if len(xrayJson.Vulnerabilities) > 0 || len(xrayJson.SecurityViolations) > 0 || len(xrayJson.LicensesViolations) > 0 {
- if err = extractXrayIssuesToSarifRun(xrayRun, xrayJson); err != nil {
+ if err = extractXrayIssuesToSarifRun(results, xrayRun, xrayJson); err != nil {
return
}
}
@@ -236,26 +244,26 @@ func convertXrayResponsesToSarifRun(results *Results, isMultipleRoots, includeLi
return
}
-func extractXrayIssuesToSarifRun(run *sarif.Run, xrayJson formats.SimpleJsonResults) error {
+func extractXrayIssuesToSarifRun(results *Results, run *sarif.Run, xrayJson formats.SimpleJsonResults) error {
for _, vulnerability := range xrayJson.Vulnerabilities {
- if err := addXrayCveIssueToSarifRun(vulnerability, run); err != nil {
+ if err := addXrayCveIssueToSarifRun(results, vulnerability, run); err != nil {
return err
}
}
for _, violation := range xrayJson.SecurityViolations {
- if err := addXrayCveIssueToSarifRun(violation, run); err != nil {
+ if err := addXrayCveIssueToSarifRun(results, violation, run); err != nil {
return err
}
}
for _, license := range xrayJson.LicensesViolations {
- if err := addXrayLicenseViolationToSarifRun(license, run); err != nil {
+ if err := addXrayLicenseViolationToSarifRun(results, license, run); err != nil {
return err
}
}
return nil
}
-func addXrayCveIssueToSarifRun(issue formats.VulnerabilityOrViolationRow, run *sarif.Run) (err error) {
+func addXrayCveIssueToSarifRun(results *Results, issue formats.VulnerabilityOrViolationRow, run *sarif.Run) (err error) {
maxCveScore, err := findMaxCVEScore(issue.Cves)
if err != nil {
return
@@ -271,6 +279,7 @@ func addXrayCveIssueToSarifRun(issue formats.VulnerabilityOrViolationRow, run *s
cveId := GetIssueIdentifier(issue.Cves, issue.IssueId)
markdownDescription := getSarifTableDescription(formattedDirectDependencies, maxCveScore, issue.Applicable, issue.FixedVersions)
addXrayIssueToSarifRun(
+ results.ResultType,
cveId,
issue.ImpactedDependencyName,
issue.ImpactedDependencyVersion,
@@ -286,12 +295,13 @@ func addXrayCveIssueToSarifRun(issue formats.VulnerabilityOrViolationRow, run *s
return
}
-func addXrayLicenseViolationToSarifRun(license formats.LicenseRow, run *sarif.Run) (err error) {
+func addXrayLicenseViolationToSarifRun(results *Results, license formats.LicenseRow, run *sarif.Run) (err error) {
formattedDirectDependencies, err := getDirectDependenciesFormatted(license.Components)
if err != nil {
return
}
addXrayIssueToSarifRun(
+ results.ResultType,
license.LicenseKey,
license.ImpactedDependencyName,
license.ImpactedDependencyVersion,
@@ -307,21 +317,29 @@ func addXrayLicenseViolationToSarifRun(license formats.LicenseRow, run *sarif.Ru
return
}
-func addXrayIssueToSarifRun(issueId, impactedDependencyName, impactedDependencyVersion string, severity severityutils.Severity, severityScore, summary, title, markdownDescription string, components []formats.ComponentRow, location *sarif.Location, run *sarif.Run) {
+func addXrayIssueToSarifRun(resultType CommandType, issueId, impactedDependencyName, impactedDependencyVersion string, severity severityutils.Severity, severityScore, summary, title, markdownDescription string, components []formats.ComponentRow, location *sarif.Location, run *sarif.Run) {
// Add rule if not exists
ruleId := getXrayIssueSarifRuleId(impactedDependencyName, impactedDependencyVersion, issueId)
if rule, _ := run.GetRuleById(ruleId); rule == nil {
addXrayRule(ruleId, title, severityScore, summary, markdownDescription, run)
}
// Add result for each component
-
for _, directDependency := range components {
msg := getXrayIssueSarifHeadline(directDependency.Name, directDependency.Version, issueId)
if result := run.CreateResultForRule(ruleId).WithMessage(sarif.NewTextMessage(msg)).WithLevel(severityutils.SeverityToSarifSeverityLevel(severity).String()); location != nil {
+ if resultType == DockerImage {
+ algorithm, layer := getLayerContentFromComponentId(directDependency.Name)
+ if layer != "" {
+ logicalLocation := sarifutils.NewLogicalLocation(layer, "layer")
+ if algorithm != "" {
+ logicalLocation.Properties = map[string]interface{}{"algorithm": algorithm}
+ }
+ location.LogicalLocations = append(location.LogicalLocations, logicalLocation)
+ }
+ }
result.AddLocation(location)
}
}
-
}
func getDescriptorFullPath(tech techutils.Technology, run *sarif.Run) (string, error) {
@@ -459,7 +477,7 @@ func getXrayIssueSarifRuleId(depName, version, key string) string {
}
func getXrayIssueSarifHeadline(depName, version, key string) string {
- return fmt.Sprintf("[%s] %s %s", key, depName, version)
+ return strings.TrimSpace(fmt.Sprintf("[%s] %s %s", key, depName, version))
}
func getXrayLicenseSarifHeadline(depName, version, key string) string {
@@ -520,6 +538,319 @@ func findMaxCVEScore(cves []formats.CveRow) (string, error) {
return strCve, nil
}
+func patchRules(subScanType SubScanType, cmdResults *Results, rules ...*sarif.ReportingDescriptor) (patched []*sarif.ReportingDescriptor) {
+ patched = []*sarif.ReportingDescriptor{}
+ for _, rule := range rules {
+ // Github code scanning ingestion rules rejects rules without help content.
+ // Patch by transferring the full description to the help field.
+ if rule.Help == nil && rule.FullDescription != nil {
+ rule.Help = rule.FullDescription
+ }
+ // SARIF1001 - if both 'id' and 'name' are present, they must be different. If they are identical, the tool must omit the 'name' property.
+ if rule.Name != nil && rule.ID == *rule.Name {
+ rule.Name = nil
+ }
+ if cmdResults.ResultType.IsTargetBinary() && subScanType == SecretsScan {
+ // Patch the rule name in case of binary scan
+ sarifutils.SetRuleShortDescriptionText(fmt.Sprintf("[Secret in Binary found] %s", sarifutils.GetRuleShortDescriptionText(rule)), rule)
+ }
+ patched = append(patched, rule)
+ }
+ return
+}
+
+func patchResults(subScanType SubScanType, cmdResults *Results, run *sarif.Run, results ...*sarif.Result) (patched []*sarif.Result) {
+ patched = []*sarif.Result{}
+ for _, result := range results {
+ if len(result.Locations) == 0 {
+ // Github code scanning ingestion rules rejects results without locations.
+ // Patch by removing results without locations.
+ log.Debug(fmt.Sprintf("[%s] Removing result [ruleId=%s] without locations: %s", subScanType.String(), sarifutils.GetResultRuleId(result), sarifutils.GetResultMsgText(result)))
+ continue
+ }
+ if cmdResults.ResultType.IsTargetBinary() {
+ var markdown string
+ if subScanType == SecretsScan {
+ markdown = getSecretInBinaryMarkdownMsg(cmdResults, result)
+ } else {
+ markdown = getScaInBinaryMarkdownMsg(cmdResults, result)
+ }
+ sarifutils.SetResultMsgMarkdown(markdown, result)
+ // For Binary scans, override the physical location if applicable (after data already used for markdown)
+ convertBinaryPhysicalLocations(cmdResults, run, result)
+ // Calculate the fingerprints if not exists
+ if !sarifutils.IsFingerprintsExists(result) {
+ if err := calculateResultFingerprints(cmdResults.ResultType, run, result); err != nil {
+ log.Warn(fmt.Sprintf("Failed to calculate the fingerprint for result [ruleId=%s]: %s", sarifutils.GetResultRuleId(result), err.Error()))
+ }
+ }
+ }
+ patched = append(patched, result)
+ }
+ return patched
+}
+
+func patchRunsToPassIngestionRules(subScanType SubScanType, cmdResults *Results, runs ...*sarif.Run) []*sarif.Run {
+ // Since we run in temp directories files should be relative
+ // Patch by converting the file paths to relative paths according to the invocations
+ convertPaths(cmdResults.ResultType, subScanType, runs...)
+ for _, run := range runs {
+ if cmdResults.ResultType.IsTargetBinary() && subScanType == SecretsScan {
+ // Patch the tool name in case of binary scan
+ sarifutils.SetRunToolName(patchedBinarySecretScannerToolName, run)
+ }
+ run.Tool.Driver.Rules = patchRules(subScanType, cmdResults, run.Tool.Driver.Rules...)
+ run.Results = patchResults(subScanType, cmdResults, run, run.Results...)
+ }
+ return runs
+}
+
+func convertPaths(commandType CommandType, subScanType SubScanType, runs ...*sarif.Run) {
+ // Convert base on invocation for source code
+ sarifutils.ConvertRunsPathsToRelative(runs...)
+ if !(commandType == DockerImage && subScanType == SecretsScan) {
+ return
+ }
+ for _, run := range runs {
+ for _, result := range run.Results {
+ // For Docker secret scan, patch the logical location if not exists
+ patchDockerSecretLocations(result)
+ }
+ }
+}
+
+// Patch the URI to be the file path from sha//
+// Extract the layer from the location URI, adds it as a logical location kind "layer"
+func patchDockerSecretLocations(result *sarif.Result) {
+ for _, location := range result.Locations {
+ algorithm, layerHash, relativePath := getLayerContentFromPath(sarifutils.GetLocationFileName(location))
+ if layerHash != "" {
+ // Set Logical location kind "layer" with the layer hash
+ logicalLocation := sarifutils.NewLogicalLocation(layerHash, "layer")
+ if algorithm != "" {
+ logicalLocation.Properties = sarif.Properties(map[string]interface{}{"algorithm": algorithm})
+ }
+ location.LogicalLocations = append(location.LogicalLocations, logicalLocation)
+ }
+ if relativePath != "" {
+ sarifutils.SetLocationFileName(location, relativePath)
+ }
+ }
+}
+
+func convertBinaryPhysicalLocations(cmdResults *Results, run *sarif.Run, result *sarif.Result) {
+ if patchedLocation := getPatchedBinaryLocation(cmdResults, run); patchedLocation != "" {
+ for _, location := range result.Locations {
+ // Patch the location - Reset the uri and region
+ location.PhysicalLocation = sarifutils.NewPhysicalLocation(patchedLocation)
+ }
+ }
+}
+
+func getPatchedBinaryLocation(cmdResults *Results, run *sarif.Run) (patchedLocation string) {
+ if cmdResults.ResultType == DockerImage {
+ if patchedLocation = getDockerfileLocationIfExists(run); patchedLocation != "" {
+ return
+ }
+ }
+ return getWorkflowFileLocationIfExists()
+}
+
+func getDockerfileLocationIfExists(run *sarif.Run) string {
+ potentialLocations := []string{filepath.Clean("Dockerfile"), sarifutils.GetFullLocationFileName("Dockerfile", run.Invocations)}
+ for _, location := range potentialLocations {
+ if exists, err := fileutils.IsFileExists(location, false); err == nil && exists {
+ return location
+ }
+ }
+ if workspace := os.Getenv(CurrentWorkflowWorkspaceEnvVar); workspace != "" {
+ if exists, err := fileutils.IsFileExists(filepath.Join(workspace, "Dockerfile"), false); err == nil && exists {
+ return filepath.Join(workspace, "Dockerfile")
+ }
+ }
+ return ""
+}
+
+func getGithubWorkflowsDirIfExists() string {
+ if exists, err := fileutils.IsDirExists(GithubBaseWorkflowDir, false); err == nil && exists {
+ return GithubBaseWorkflowDir
+ }
+ if workspace := os.Getenv(CurrentWorkflowWorkspaceEnvVar); workspace != "" {
+ if exists, err := fileutils.IsDirExists(filepath.Join(workspace, GithubBaseWorkflowDir), false); err == nil && exists {
+ return filepath.Join(workspace, GithubBaseWorkflowDir)
+ }
+ }
+ return ""
+}
+
+func getWorkflowFileLocationIfExists() (location string) {
+ workflowName := os.Getenv(CurrentWorkflowNameEnvVar)
+ if workflowName == "" {
+ return
+ }
+ workflowsDir := getGithubWorkflowsDirIfExists()
+ if workflowsDir == "" {
+ return
+ }
+ currentWd, err := os.Getwd()
+ if err != nil {
+ log.Warn(fmt.Sprintf("Failed to get the current working directory to get workflow file location: %s", err.Error()))
+ return
+ }
+ // Check if exists in the .github/workflows directory as file name or in the content, return the file path or empty string
+ if files, err := fileutils.ListFiles(workflowsDir, false); err == nil && len(files) > 0 {
+ for _, file := range files {
+ if strings.Contains(file, workflowName) {
+ return strings.TrimPrefix(file, currentWd)
+ }
+ }
+ for _, file := range files {
+ if content, err := fileutils.ReadFile(file); err == nil && strings.Contains(string(content), workflowName) {
+ return strings.TrimPrefix(file, currentWd)
+ }
+ }
+ }
+ return
+}
+
+func getSecretInBinaryMarkdownMsg(cmdResults *Results, result *sarif.Result) string {
+ if cmdResults.ResultType != Binary && cmdResults.ResultType != DockerImage {
+ return ""
+ }
+ content := "🔒 Found Secrets in Binary"
+ if cmdResults.ResultType == DockerImage {
+ content += " docker"
+ }
+ content += " scanning:"
+ return content + getBaseBinaryDescriptionMarkdown(SecretsScan, cmdResults, result)
+}
+
+func getScaInBinaryMarkdownMsg(cmdResults *Results, result *sarif.Result) string {
+ return sarifutils.GetResultMsgText(result) + getBaseBinaryDescriptionMarkdown(ScaScan, cmdResults, result)
+}
+
+func getBaseBinaryDescriptionMarkdown(subScanType SubScanType, cmdResults *Results, result *sarif.Result) (content string) {
+ // If in github action, add the workflow name and run number
+ if workflowLocation := getWorkflowFileLocationIfExists(); workflowLocation != "" {
+ content += fmt.Sprintf("\nGithub Actions Workflow: %s", workflowLocation)
+ }
+ if os.Getenv(CurrentWorkflowRunNumberEnvVar) != "" {
+ content += fmt.Sprintf("\nRun: %s", os.Getenv(CurrentWorkflowRunNumberEnvVar))
+ }
+ // If is docker image, add the image tag
+ if cmdResults.ResultType == DockerImage {
+ if imageTag := getDockerImageTag(cmdResults); imageTag != "" {
+ content += fmt.Sprintf("\nImage: %s", imageTag)
+ }
+ }
+ var location *sarif.Location
+ if len(result.Locations) > 0 {
+ location = result.Locations[0]
+ }
+ return content + getBinaryLocationMarkdownString(cmdResults.ResultType, subScanType, location)
+}
+
+func getDockerImageTag(cmdResults *Results) string {
+ if cmdResults.ResultType != DockerImage || len(cmdResults.ScaResults) == 0 {
+ return ""
+ }
+ for _, scaResults := range cmdResults.ScaResults {
+ if scaResults.Name != "" {
+ return scaResults.Name
+ }
+ }
+ return filepath.Base(cmdResults.ScaResults[0].Target)
+}
+
+// If command is docker prepare the markdown string for the location:
+// * Layer:
+// * Filepath:
+// * Evidence:
+func getBinaryLocationMarkdownString(commandType CommandType, subScanType SubScanType, location *sarif.Location) (content string) {
+ if location == nil {
+ return ""
+ }
+ if commandType == DockerImage {
+ if layer, algorithm := getDockerLayer(location); layer != "" {
+ if algorithm != "" {
+ content += fmt.Sprintf("\nLayer (%s): %s", algorithm, layer)
+ } else {
+ content += fmt.Sprintf("\nLayer: %s", layer)
+ }
+ }
+ }
+ if subScanType != SecretsScan {
+ return
+ }
+ if locationFilePath := sarifutils.GetLocationFileName(location); locationFilePath != "" {
+ content += fmt.Sprintf("\nFilepath: %s", locationFilePath)
+ }
+ if snippet := sarifutils.GetLocationSnippet(location); snippet != "" {
+ content += fmt.Sprintf("\nEvidence: %s", snippet)
+ }
+ return
+}
+
+func getDockerLayer(location *sarif.Location) (layer, algorithm string) {
+ // If location has logical location with kind "layer" return it
+ if logicalLocation := sarifutils.GetLogicalLocation("layer", location); logicalLocation != nil && logicalLocation.Name != nil {
+ layer = *logicalLocation.Name
+ if algorithmValue, ok := logicalLocation.Properties["algorithm"].(string); ok {
+ algorithm = algorithmValue
+ }
+ return
+ }
+ return
+}
+
+// Match: >
+// Extract algorithm, hash and relative path
+func getLayerContentFromPath(content string) (algorithm string, layerHash string, relativePath string) {
+ matches := dockerJasLocationPathPattern.FindStringSubmatch(content)
+ if len(matches) == 0 {
+ return
+ }
+ algorithm = matches[dockerJasLocationPathPattern.SubexpIndex("algorithm")]
+ layerHash = matches[dockerJasLocationPathPattern.SubexpIndex("hash")]
+ relativePath = matches[dockerJasLocationPathPattern.SubexpIndex("relativePath")]
+ return
+}
+
+// Match: >://:/>
+// Extract algorithm and hash
+func getLayerContentFromComponentId(componentId string) (algorithm string, layerHash string) {
+ matches := dockerScaComponentNamePattern.FindStringSubmatch(componentId)
+ if len(matches) == 0 {
+ return
+ }
+ algorithm = matches[dockerScaComponentNamePattern.SubexpIndex("algorithm")]
+ layerHash = matches[dockerScaComponentNamePattern.SubexpIndex("hash")]
+ return
+}
+
+// According to the SARIF specification:
+// To determine whether a result from a subsequent run is logically the same as a result from the baseline,
+// there must be a way to use information contained in the result to construct a stable identifier for the result. We refer to this identifier as a fingerprint.
+// A result management system SHOULD construct a fingerprint by using information contained in the SARIF file such as:
+// The name of the tool that produced the result, the rule id, the file system path to the analysis target...
+func calculateResultFingerprints(resultType CommandType, run *sarif.Run, result *sarif.Result) error {
+ if !resultType.IsTargetBinary() {
+ return nil
+ }
+ ids := []string{sarifutils.GetRunToolName(run), sarifutils.GetResultRuleId(result)}
+ for _, location := range sarifutils.GetResultFileLocations(result) {
+ ids = append(ids, strings.ReplaceAll(location, string(filepath.Separator), "/"))
+ }
+ ids = append(ids, sarifutils.GetResultLocationSnippets(result)...)
+ // Calculate the hash value and set the fingerprint to the result
+ hashValue, err := Md5Hash(ids...)
+ if err != nil {
+ return err
+ }
+ sarifutils.SetResultFingerprint(jfrogFingerprintAlgorithmName, hashValue, result)
+ return nil
+}
+
// Splits scan responses into aggregated lists of violations, vulnerabilities and licenses.
func SplitScanResults(results []*ScaScanResult) ([]services.Violation, []services.Vulnerability, []services.License) {
var violations []services.Violation
@@ -546,7 +877,7 @@ func writeJsonResults(results *Results) (resultsPath string, err error) {
err = e
}
}()
- bytesRes, err := JSONMarshal(&results)
+ bytesRes, err := JSONMarshalNotEscaped(&results)
if errorutils.CheckError(err) != nil {
return
}
@@ -563,7 +894,20 @@ func writeJsonResults(results *Results) (resultsPath string, err error) {
return
}
-func JSONMarshal(t interface{}) ([]byte, error) {
+func WriteSarifResultsAsString(report *sarif.Report, escape bool) (sarifStr string, err error) {
+ var out []byte
+ if escape {
+ out, err = json.Marshal(report)
+ } else {
+ out, err = JSONMarshalNotEscaped(report)
+ }
+ if err != nil {
+ return "", errorutils.CheckError(err)
+ }
+ return clientUtils.IndentJson(out), nil
+}
+
+func JSONMarshalNotEscaped(t interface{}) ([]byte, error) {
buffer := &bytes.Buffer{}
encoder := json.NewEncoder(buffer)
encoder.SetEscapeHTML(false)
@@ -572,7 +916,7 @@ func JSONMarshal(t interface{}) ([]byte, error) {
}
func PrintJson(output interface{}) error {
- results, err := JSONMarshal(output)
+ results, err := JSONMarshalNotEscaped(output)
if err != nil {
return errorutils.CheckError(err)
}
@@ -581,11 +925,11 @@ func PrintJson(output interface{}) error {
}
func PrintSarif(results *Results, isMultipleRoots, includeLicenses bool) error {
- sarifReport, err := GenereateSarifReportFromResults(results, isMultipleRoots, includeLicenses, nil)
+ sarifReport, err := GenerateSarifReportFromResults(results, isMultipleRoots, includeLicenses, nil)
if err != nil {
return err
}
- sarifFile, err := sarifutils.ConvertSarifReportToString(sarifReport)
+ sarifFile, err := WriteSarifResultsAsString(sarifReport, false)
if err != nil {
return err
}
@@ -770,7 +1114,11 @@ func getSecuritySummaryFindings(cves []services.Cve, issueId string, components
}
if len(cves) == 0 {
// XRAY-ID, no scanners for them
- uniqueFindings[jasutils.NotCovered.String()] += 1
+ status := jasutils.NotScanned
+ if len(applicableRuns) > 0 {
+ status = jasutils.NotCovered
+ }
+ uniqueFindings[status.String()] += 1
}
return uniqueFindings
}
diff --git a/utils/resultwriter_test.go b/utils/resultwriter_test.go
index 5f8652b0..d009c777 100644
--- a/utils/resultwriter_test.go
+++ b/utils/resultwriter_test.go
@@ -1,6 +1,7 @@
package utils
import (
+ "fmt"
"os"
"path/filepath"
"sort"
@@ -11,6 +12,8 @@ import (
"github.com/jfrog/jfrog-cli-security/formats/sarifutils"
"github.com/jfrog/jfrog-cli-security/utils/jasutils"
"github.com/jfrog/jfrog-cli-security/utils/techutils"
+ "github.com/jfrog/jfrog-client-go/utils/io/fileutils"
+ clientTests "github.com/jfrog/jfrog-client-go/utils/tests"
"github.com/jfrog/jfrog-client-go/xray/services"
"github.com/owenrumney/go-sarif/v2/sarif"
"github.com/stretchr/testify/assert"
@@ -391,7 +394,7 @@ func TestConvertXrayScanToSimpleJson(t *testing.T) {
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
- results := NewAuditResults()
+ results := NewAuditResults(SourceCode)
scaScanResult := ScaScanResult{XrayResults: []services.ScanResponse{tc.result}}
results.ScaResults = append(results.ScaResults, &scaScanResult)
output, err := ConvertXrayScanToSimpleJson(results, false, tc.includeLicenses, true, tc.allowedLicenses)
@@ -436,7 +439,7 @@ func TestJSONMarshall(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.testName, func(t *testing.T) {
- printedString, err := JSONMarshal(tc.resultString)
+ printedString, err := JSONMarshalNotEscaped(tc.resultString)
assert.NoError(t, err)
assert.Equal(t, tc.expectedResult, string(printedString))
})
@@ -573,6 +576,273 @@ func TestGetSummary(t *testing.T) {
}
}
+func TestGetLayerContentFromComponentId(t *testing.T) {
+ testCases := []struct {
+ name string
+ path string
+ expectedAlgorithm string
+ expectedLayerHash string
+ }{
+ {
+ name: "Valid path",
+ path: "sha256__cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e.tar",
+ expectedAlgorithm: "sha256",
+ expectedLayerHash: "cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e",
+ },
+ {
+ name: "Invalid path - not hex",
+ path: "sha256__NOT_HEX.tar",
+ },
+ {
+ name: "Invalid path - no algorithm",
+ path: "_cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e.tar",
+ },
+ {
+ name: "Invalid path - no suffix",
+ path: "sha256__cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e",
+ },
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ algorithm, layerHash := getLayerContentFromComponentId(tc.path)
+ assert.Equal(t, tc.expectedAlgorithm, algorithm)
+ assert.Equal(t, tc.expectedLayerHash, layerHash)
+ })
+ }
+}
+
+func preparePatchTestEnv(t *testing.T) (string, string, func()) {
+ currentWd, err := os.Getwd()
+ assert.NoError(t, err)
+ wd, cleanUpTempDir := tests.CreateTempDirWithCallbackAndAssert(t)
+ cleanUpWd := clientTests.ChangeDirWithCallback(t, currentWd, wd)
+ dockerfileDir := filepath.Join(wd, "DockerfileDir")
+ err = fileutils.CreateDirIfNotExist(dockerfileDir)
+ // Prepare env content
+ assert.NoError(t, err)
+ createDummyDockerfile(t, dockerfileDir)
+ createDummyGithubWorkflow(t, dockerfileDir)
+ createDummyGithubWorkflow(t, wd)
+ return wd, dockerfileDir, func() {
+ cleanUpWd()
+ cleanUpTempDir()
+ }
+}
+
+func createDummyGithubWorkflow(t *testing.T, baseDir string) {
+ assert.NoError(t, fileutils.CreateDirIfNotExist(filepath.Join(baseDir, GithubBaseWorkflowDir)))
+ assert.NoError(t, os.WriteFile(filepath.Join(baseDir, GithubBaseWorkflowDir, "workflowFile.yml"), []byte("workflow name"), 0644))
+}
+
+func createDummyDockerfile(t *testing.T, baseDir string) {
+ assert.NoError(t, os.WriteFile(filepath.Join(baseDir, "Dockerfile"), []byte("Dockerfile data"), 0644))
+}
+
+func TestPatchRunsToPassIngestionRules(t *testing.T) {
+ wd, dockerfileDir, cleanUp := preparePatchTestEnv(t)
+ defer cleanUp()
+
+ testCases := []struct {
+ name string
+ cmdResult *Results
+ subScan SubScanType
+ withEnvVars bool
+ withDockerfile bool
+ input []*sarif.Run
+ expectedResults []*sarif.Run
+ }{
+ {
+ name: "No runs",
+ cmdResult: &Results{ResultType: DockerImage, ScaResults: []*ScaScanResult{{Name: "dockerImage:imageVersion"}}},
+ subScan: SecretsScan,
+ input: []*sarif.Run{},
+ expectedResults: []*sarif.Run{},
+ },
+ {
+ name: "Build scan - SCA",
+ cmdResult: &Results{ResultType: Build, ScaResults: []*ScaScanResult{{Name: "buildName (buildNumber)"}}},
+ subScan: ScaScan,
+ input: []*sarif.Run{
+ sarifutils.CreateRunWithDummyResultsInWd(wd, sarifutils.CreateDummyResultInPath(fmt.Sprintf("file://%s", filepath.Join(wd, "dir", "file")))),
+ },
+ expectedResults: []*sarif.Run{
+ sarifutils.CreateRunWithDummyResultsInWd(wd, sarifutils.CreateDummyResultInPath(filepath.Join("dir", "file"))),
+ },
+ },
+ {
+ name: "Docker image scan - SCA",
+ cmdResult: &Results{ResultType: DockerImage, ScaResults: []*ScaScanResult{{Name: "dockerImage:imageVersion"}}},
+ subScan: ScaScan,
+ input: []*sarif.Run{
+ sarifutils.CreateRunWithDummyResultAndRuleProperties("applicability", "applicable", sarifutils.CreateDummyResultWithPathAndLogicalLocation("sha256__f752cb05a39e65f231a3c47c2e08cbeac1c15e4daff0188cb129c12a3ea3049d", "f752cb05a39e65f231a3c47c2e08cbeac1c15e4daff0188cb129c12a3ea3049d", "layer", "algorithm", "sha256").WithMessage(sarif.NewTextMessage("some-msg"))).
+ WithInvocations([]*sarif.Invocation{
+ sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation(wd)),
+ },
+ ),
+ sarifutils.CreateRunWithDummyResultsInWd(wd,
+ sarifutils.CreateDummyResultWithPathAndLogicalLocation("sha256__f752cb05a39e65f231a3c47c2e08cbeac1c15e4daff0188cb129c12a3ea3049d", "f752cb05a39e65f231a3c47c2e08cbeac1c15e4daff0188cb129c12a3ea3049d", "layer", "algorithm", "sha256").WithMessage(sarif.NewTextMessage("some-msg")),
+ ),
+ },
+ expectedResults: []*sarif.Run{
+ sarifutils.CreateRunWithDummyResultAndRuleProperties("applicability", "applicable",
+ sarifutils.CreateDummyResultWithFingerprint("some-msg\nImage: dockerImage:imageVersion\nLayer (sha256): f752cb05a39e65f231a3c47c2e08cbeac1c15e4daff0188cb129c12a3ea3049d", "some-msg", jfrogFingerprintAlgorithmName, "9522c1d915eef55b4a0dc9e160bf5dc7",
+ sarifutils.CreateDummyLocationWithPathAndLogicalLocation("sha256__f752cb05a39e65f231a3c47c2e08cbeac1c15e4daff0188cb129c12a3ea3049d", "f752cb05a39e65f231a3c47c2e08cbeac1c15e4daff0188cb129c12a3ea3049d", "layer", "algorithm", "sha256"),
+ ),
+ ).WithInvocations([]*sarif.Invocation{
+ sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation(wd)),
+ }),
+ sarifutils.CreateRunWithDummyResultsInWd(wd,
+ sarifutils.CreateDummyResultWithFingerprint("some-msg\nImage: dockerImage:imageVersion\nLayer (sha256): f752cb05a39e65f231a3c47c2e08cbeac1c15e4daff0188cb129c12a3ea3049d", "some-msg", jfrogFingerprintAlgorithmName, "9522c1d915eef55b4a0dc9e160bf5dc7",
+ sarifutils.CreateDummyLocationWithPathAndLogicalLocation("sha256__f752cb05a39e65f231a3c47c2e08cbeac1c15e4daff0188cb129c12a3ea3049d", "f752cb05a39e65f231a3c47c2e08cbeac1c15e4daff0188cb129c12a3ea3049d", "layer", "algorithm", "sha256"),
+ ),
+ ),
+ },
+ },
+ {
+ name: "Docker image scan - with env vars",
+ cmdResult: &Results{ResultType: DockerImage, ScaResults: []*ScaScanResult{{Name: "dockerImage:imageVersion"}}},
+ subScan: ScaScan,
+ withEnvVars: true,
+ input: []*sarif.Run{
+ sarifutils.CreateRunWithDummyResultsInWd(wd,
+ sarifutils.CreateDummyResultWithPathAndLogicalLocation("sha256__f752cb05a39e65f231a3c47c2e08cbeac1c15e4daff0188cb129c12a3ea3049d", "f752cb05a39e65f231a3c47c2e08cbeac1c15e4daff0188cb129c12a3ea3049d", "layer", "algorithm", "sha256").WithMessage(sarif.NewTextMessage("some-msg")),
+ // No location, should be removed in the output
+ sarifutils.CreateDummyResult("some-markdown", "some-other-msg", "rule", "level"),
+ ),
+ },
+ expectedResults: []*sarif.Run{
+ sarifutils.CreateRunWithDummyResultsInWd(wd,
+ sarifutils.CreateDummyResultWithFingerprint(fmt.Sprintf("some-msg\nGithub Actions Workflow: %s\nRun: 123\nImage: dockerImage:imageVersion\nLayer (sha256): f752cb05a39e65f231a3c47c2e08cbeac1c15e4daff0188cb129c12a3ea3049d", filepath.Join(GithubBaseWorkflowDir, "workflowFile.yml")), "some-msg", jfrogFingerprintAlgorithmName, "eda26ae830c578197aeda65a82d7f093",
+ sarifutils.CreateDummyLocationWithPathAndLogicalLocation("", "f752cb05a39e65f231a3c47c2e08cbeac1c15e4daff0188cb129c12a3ea3049d", "layer", "algorithm", "sha256").WithPhysicalLocation(
+ sarif.NewPhysicalLocation().WithArtifactLocation(sarif.NewSimpleArtifactLocation(filepath.Join(GithubBaseWorkflowDir, "workflowFile.yml"))),
+ ),
+ ),
+ ),
+ },
+ },
+ {
+ name: "Docker image scan - with Dockerfile in wd",
+ cmdResult: &Results{ResultType: DockerImage, ScaResults: []*ScaScanResult{{Name: "dockerImage:imageVersion"}}},
+ subScan: ScaScan,
+ withEnvVars: true,
+ withDockerfile: true,
+ input: []*sarif.Run{
+ sarifutils.CreateRunWithDummyResultsInWd(dockerfileDir,
+ sarifutils.CreateDummyResultWithPathAndLogicalLocation("sha256__f752cb05a39e65f231a3c47c2e08cbeac1c15e4daff0188cb129c12a3ea3049d", "f752cb05a39e65f231a3c47c2e08cbeac1c15e4daff0188cb129c12a3ea3049d", "layer", "algorithm", "sha256").WithMessage(sarif.NewTextMessage("some-msg")),
+ ),
+ },
+ expectedResults: []*sarif.Run{
+ sarifutils.CreateRunWithDummyResultsInWd(dockerfileDir,
+ sarifutils.CreateDummyResultWithFingerprint(fmt.Sprintf("some-msg\nGithub Actions Workflow: %s\nRun: 123\nImage: dockerImage:imageVersion\nLayer (sha256): f752cb05a39e65f231a3c47c2e08cbeac1c15e4daff0188cb129c12a3ea3049d", filepath.Join(GithubBaseWorkflowDir, "workflowFile.yml")), "some-msg", jfrogFingerprintAlgorithmName, "8cbd7268a4d20f2358ba2667ebd18956",
+ sarifutils.CreateDummyLocationWithPathAndLogicalLocation("", "f752cb05a39e65f231a3c47c2e08cbeac1c15e4daff0188cb129c12a3ea3049d", "layer", "algorithm", "sha256").WithPhysicalLocation(
+ sarif.NewPhysicalLocation().WithArtifactLocation(sarif.NewSimpleArtifactLocation("Dockerfile")),
+ ),
+ ),
+ ),
+ },
+ },
+ {
+ name: "Docker image scan - Secrets",
+ cmdResult: &Results{ResultType: DockerImage, ScaResults: []*ScaScanResult{{Name: "dockerImage:imageVersion"}}},
+ subScan: SecretsScan,
+ input: []*sarif.Run{
+ sarifutils.CreateRunNameWithResults("some tool name",
+ sarifutils.CreateDummyResultInPath(fmt.Sprintf("file://%s", filepath.Join(wd, "unpacked", "filesystem", "blobs", "sha1", "9e88ea9de1b44baba5e96a79e33e4af64334b2bf129e838e12f6dae71b5c86f0", "usr", "src", "app", "server", "index.js"))),
+ ).WithInvocations([]*sarif.Invocation{
+ sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation(wd)),
+ }),
+ },
+ expectedResults: []*sarif.Run{
+ {
+ Tool: sarif.Tool{
+ Driver: sarifutils.CreateDummyDriver(patchedBinarySecretScannerToolName, "", &sarif.ReportingDescriptor{
+ ID: "rule",
+ ShortDescription: sarif.NewMultiformatMessageString("[Secret in Binary found] "),
+ }),
+ },
+ Invocations: []*sarif.Invocation{sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation(wd))},
+ Results: []*sarif.Result{
+ sarifutils.CreateDummyResultWithFingerprint(fmt.Sprintf("🔒 Found Secrets in Binary docker scanning:\nImage: dockerImage:imageVersion\nLayer (sha1): 9e88ea9de1b44baba5e96a79e33e4af64334b2bf129e838e12f6dae71b5c86f0\nFilepath: %s\nEvidence: snippet", filepath.Join("usr", "src", "app", "server", "index.js")), "", jfrogFingerprintAlgorithmName, "dee156c9fd75a4237102dc8fb29277a2",
+ sarifutils.CreateDummyLocationWithPathAndLogicalLocation(filepath.Join("usr", "src", "app", "server", "index.js"), "9e88ea9de1b44baba5e96a79e33e4af64334b2bf129e838e12f6dae71b5c86f0", "layer", "algorithm", "sha1"),
+ ),
+ },
+ },
+ },
+ },
+ {
+ name: "Binary scan - SCA",
+ cmdResult: &Results{ResultType: Binary, ScaResults: []*ScaScanResult{{Target: filepath.Join(wd, "dir", "binary")}}},
+ subScan: ScaScan,
+ input: []*sarif.Run{
+ sarifutils.CreateRunWithDummyResultsInWd(wd,
+ sarifutils.CreateDummyResultInPath(fmt.Sprintf("file://%s", filepath.Join(wd, "dir", "binary"))),
+ ),
+ },
+ expectedResults: []*sarif.Run{
+ sarifutils.CreateRunWithDummyResultsInWd(wd,
+ sarifutils.CreateDummyResultWithFingerprint("", "", jfrogFingerprintAlgorithmName, "e72a936dc73acbc4283a93230ff9b6e8", sarifutils.CreateDummyLocationInPath(filepath.Join("dir", "binary"))),
+ ),
+ },
+ },
+ {
+ name: "Audit scan - SCA",
+ cmdResult: &Results{ResultType: SourceCode, ScaResults: []*ScaScanResult{{Target: wd}}},
+ subScan: ScaScan,
+ input: []*sarif.Run{
+ sarifutils.CreateRunWithDummyResultsInWd(wd,
+ sarifutils.CreateDummyResultInPath(filepath.Join(wd, "Package-Descriptor")),
+ // No location, should be removed in the output
+ sarifutils.CreateDummyResult("some-markdown", "some-other-msg", "rule", "level"),
+ ),
+ },
+ expectedResults: []*sarif.Run{
+ sarifutils.CreateRunWithDummyResultsInWd(wd,
+ sarifutils.CreateDummyResultInPath("Package-Descriptor"),
+ ),
+ },
+ },
+ {
+ name: "Audit scan - Secrets",
+ cmdResult: &Results{ResultType: SourceCode, ScaResults: []*ScaScanResult{{Target: wd}}},
+ subScan: SecretsScan,
+ input: []*sarif.Run{
+ sarifutils.CreateRunWithDummyResultsInWd(wd,
+ sarifutils.CreateDummyResultInPath(fmt.Sprintf("file://%s", filepath.Join(wd, "dir", "file"))),
+ // No location, should be removed in the output
+ sarifutils.CreateDummyResult("some-markdown", "some-other-msg", "rule", "level"),
+ ),
+ },
+ expectedResults: []*sarif.Run{
+ sarifutils.CreateRunWithDummyResultsInWd(wd,
+ sarifutils.CreateDummyResultInPath(filepath.Join("dir", "file")),
+ ),
+ },
+ },
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ if tc.withEnvVars {
+ cleanFileEnv := clientTests.SetEnvWithCallbackAndAssert(t, CurrentWorkflowNameEnvVar, "workflow name")
+ defer cleanFileEnv()
+ cleanRunNumEnv := clientTests.SetEnvWithCallbackAndAssert(t, CurrentWorkflowRunNumberEnvVar, "123")
+ defer cleanRunNumEnv()
+ } else {
+ // Since the the env are provided by the
+ cleanFileEnv := clientTests.SetEnvWithCallbackAndAssert(t, CurrentWorkflowNameEnvVar, "")
+ defer cleanFileEnv()
+ cleanRunNumEnv := clientTests.SetEnvWithCallbackAndAssert(t, CurrentWorkflowRunNumberEnvVar, "")
+ defer cleanRunNumEnv()
+ }
+ if tc.withDockerfile {
+ revertWd := clientTests.ChangeDirWithCallback(t, wd, dockerfileDir)
+ defer revertWd()
+ }
+ patchRunsToPassIngestionRules(tc.subScan, tc.cmdResult, tc.input...)
+ assert.ElementsMatch(t, tc.expectedResults, tc.input)
+ })
+ }
+}
+
func getDummyScaTestResults(vulnerability, violation bool) (responses []services.ScanResponse) {
response := services.ScanResponse{}
if vulnerability {
diff --git a/utils/securityJobSummary.go b/utils/securityJobSummary.go
index a1a4be41..210173af 100644
--- a/utils/securityJobSummary.go
+++ b/utils/securityJobSummary.go
@@ -3,6 +3,7 @@ package utils
import (
"errors"
"fmt"
+ "os"
"path/filepath"
"sort"
"strings"
@@ -14,18 +15,16 @@ import (
"github.com/jfrog/jfrog-cli-core/v2/utils/config"
"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
"github.com/jfrog/jfrog-cli-security/formats"
+ "github.com/jfrog/jfrog-cli-security/formats/sarifutils"
"github.com/jfrog/jfrog-cli-security/resources"
"github.com/jfrog/jfrog-cli-security/utils/jasutils"
"github.com/jfrog/jfrog-cli-security/utils/severityutils"
+ "github.com/jfrog/jfrog-client-go/utils/errorutils"
+ "github.com/jfrog/jfrog-client-go/utils/log"
+ "github.com/owenrumney/go-sarif/v2/sarif"
)
const (
- Build SecuritySummarySection = "Build-info Scans"
- Binary SecuritySummarySection = "Artifact Scans"
- Modules SecuritySummarySection = "Source Code Scans"
- Docker SecuritySummarySection = "Docker Image Scans"
- Curation SecuritySummarySection = "Curation Audit"
-
PreFormat HtmlTag = "%s
"
ImgTag HtmlTag = ""
CenterContent HtmlTag = "%s
"
@@ -33,21 +32,17 @@ const (
Link HtmlTag = "%s"
NewLine HtmlTag = "
%s"
DetailsWithSummary HtmlTag = "%s
%s "
- DetailsOpenWithSummary HtmlTag = "%s
%s "
- RedColor HtmlTag = "%s"
- OrangeColor HtmlTag = "%s"
- GreenColor HtmlTag = "%s"
+ DetailsOpenWithSummary HtmlTag = "%s
%s\n "
TabTag HtmlTag = "	%s"
- ApplicableStatusCount SeverityStatus = "%d Applicable"
- NotApplicableStatusCount SeverityStatus = "%d Not Applicable"
+ ApplicableStatusCount SeverityDisplayStatus = "%d Applicable"
+ NotApplicableStatusCount SeverityDisplayStatus = "%d Not Applicable"
maxWatchesInLine = 4
)
-type SecuritySummarySection string
type HtmlTag string
-type SeverityStatus string
+type SeverityDisplayStatus string
func (c HtmlTag) Format(args ...any) string {
return fmt.Sprintf(string(c), args...)
@@ -57,7 +52,7 @@ func (c HtmlTag) FormatInt(value int) string {
return fmt.Sprintf(string(c), fmt.Sprintf("%d", value))
}
-func (s SeverityStatus) Format(count int) string {
+func (s SeverityDisplayStatus) Format(count int) string {
return fmt.Sprintf(string(s), count)
}
@@ -71,38 +66,38 @@ func getStatusIcon(failed bool) string {
type SecurityJobSummary struct{}
-func NewCurationSummary(cmdResult formats.ResultsSummary) (summary ScanCommandResultSummary) {
- summary.ResultType = Curation
- summary.Summary = cmdResult
- return
-}
-
-func newResultSummary(cmdResults *Results, section SecuritySummarySection, serverDetails *config.ServerDetails, vulnerabilitiesReqested, violationsReqested bool) (summary ScanCommandResultSummary) {
- summary.ResultType = section
+func newResultSummary(cmdResults *Results, cmdType CommandType, serverDetails *config.ServerDetails, vulnerabilitiesRequested, violationsRequested bool) (summary ScanCommandResultSummary) {
+ summary.ResultType = cmdType
summary.Args = &ResultSummaryArgs{BaseJfrogUrl: serverDetails.Url}
- summary.Summary = ToSummary(cmdResults, vulnerabilitiesReqested, violationsReqested)
+ summary.Summary = ToSummary(cmdResults, vulnerabilitiesRequested, violationsRequested)
return
}
-func NewBuildScanSummary(cmdResults *Results, serverDetails *config.ServerDetails, vulnerabilitiesReqested, violationsReqested bool, buildName, buildNumber string) (summary ScanCommandResultSummary) {
- summary = newResultSummary(cmdResults, Build, serverDetails, vulnerabilitiesReqested, violationsReqested)
+func NewBuildScanSummary(cmdResults *Results, serverDetails *config.ServerDetails, vulnerabilitiesRequested, violationsRequested bool, buildName, buildNumber string) (summary ScanCommandResultSummary) {
+ summary = newResultSummary(cmdResults, Build, serverDetails, vulnerabilitiesRequested, violationsRequested)
summary.Args.BuildName = buildName
summary.Args.BuildNumbers = []string{buildNumber}
return
}
-func NewDockerScanSummary(cmdResults *Results, serverDetails *config.ServerDetails, vulnerabilitiesReqested, violationsReqested bool, dockerImage string) (summary ScanCommandResultSummary) {
- summary = newResultSummary(cmdResults, Docker, serverDetails, vulnerabilitiesReqested, violationsReqested)
+func NewDockerScanSummary(cmdResults *Results, serverDetails *config.ServerDetails, vulnerabilitiesRequested, violationsRequested bool, dockerImage string) (summary ScanCommandResultSummary) {
+ summary = newResultSummary(cmdResults, DockerImage, serverDetails, vulnerabilitiesRequested, violationsRequested)
summary.Args.DockerImage = dockerImage
return
}
-func NewBinaryScanSummary(cmdResults *Results, serverDetails *config.ServerDetails, vulnerabilitiesReqested, violationsReqested bool) (summary ScanCommandResultSummary) {
- return newResultSummary(cmdResults, Binary, serverDetails, vulnerabilitiesReqested, violationsReqested)
+func NewBinaryScanSummary(cmdResults *Results, serverDetails *config.ServerDetails, vulnerabilitiesRequested, violationsRequested bool) (summary ScanCommandResultSummary) {
+ return newResultSummary(cmdResults, Binary, serverDetails, vulnerabilitiesRequested, violationsRequested)
}
-func NewAuditScanSummary(cmdResults *Results, serverDetails *config.ServerDetails, vulnerabilitiesReqested, violationsReqested bool) (summary ScanCommandResultSummary) {
- return newResultSummary(cmdResults, Modules, serverDetails, vulnerabilitiesReqested, violationsReqested)
+func NewAuditScanSummary(cmdResults *Results, serverDetails *config.ServerDetails, vulnerabilitiesRequested, violationsRequested bool) (summary ScanCommandResultSummary) {
+ return newResultSummary(cmdResults, SourceCode, serverDetails, vulnerabilitiesRequested, violationsRequested)
+}
+
+func NewCurationSummary(cmdResult formats.ResultsSummary) (summary ScanCommandResultSummary) {
+ summary.ResultType = Curation
+ summary.Summary = cmdResult
+ return
}
type ResultSummaryArgs struct {
@@ -133,13 +128,18 @@ func (rsa ResultSummaryArgs) ToArgs(index commandsummary.Index) (args []string)
args = append(args, rsa.BuildName)
args = append(args, rsa.BuildNumbers...)
} else if index == commandsummary.DockerScan {
- args = append(args, rsa.DockerImage)
+ image := rsa.DockerImage
+ // if user did not provide image tag, add latest
+ if !strings.Contains(image, ":") {
+ image += ":latest"
+ }
+ args = append(args, image)
}
return
}
type ScanCommandResultSummary struct {
- ResultType SecuritySummarySection `json:"resultType"`
+ ResultType CommandType `json:"resultType"`
Args *ResultSummaryArgs `json:"args,omitempty"`
Summary formats.ResultsSummary `json:"summary"`
}
@@ -149,12 +149,16 @@ func NewSecurityJobSummary() (js *commandsummary.CommandSummary, err error) {
return commandsummary.New(&SecurityJobSummary{}, "security")
}
-// Record the security command outputs
-func RecordSecurityCommandSummary(content ScanCommandResultSummary) (err error) {
+func getRecordManager() (manager *commandsummary.CommandSummary, err error) {
if !commandsummary.ShouldRecordSummary() {
return
}
- manager, err := NewSecurityJobSummary()
+ return NewSecurityJobSummary()
+}
+
+// Record the security command outputs
+func RecordSecurityCommandSummary(content ScanCommandResultSummary) (err error) {
+ manager, err := getRecordManager()
if err != nil || manager == nil {
return
}
@@ -163,12 +167,64 @@ func RecordSecurityCommandSummary(content ScanCommandResultSummary) (err error)
return
}
updateSummaryNamesToRelativePath(&content.Summary, wd)
- if index := getDataIndexFromSection(content.ResultType); index != "" {
+ if index := getDataIndexFromCommandType(content.ResultType); index != "" {
return recordIndexData(manager, content, index)
}
return manager.Record(content)
}
+func RecordSarifOutput(cmdResults *Results) (err error) {
+ manager, err := getRecordManager()
+ if err != nil || manager == nil {
+ return
+ }
+ extended := true
+ if !extended && !commandsummary.StaticMarkdownConfig.IsExtendedSummary() {
+ log.Info("Results can be uploaded to Github security tab automatically by upgrading your JFrog subscription.")
+ return
+ }
+ sarifReport, err := GenerateSarifReportFromResults(cmdResults, true, false, nil)
+ if err != nil {
+ return err
+ }
+ out, err := JSONMarshalNotEscaped(sarifReport)
+ if err != nil {
+ return errorutils.CheckError(err)
+ }
+ return manager.RecordWithIndex(out, commandsummary.SarifReport)
+}
+
+func CombineSarifOutputFiles(dataFilePaths []string) (data []byte, err error) {
+ if len(dataFilePaths) == 0 {
+ return
+ }
+ // Load the content of the files
+ reports := []*sarif.Report{}
+ for _, dataFilePath := range dataFilePaths {
+ if report, e := loadSarifReport(dataFilePath); e != nil {
+ err = errors.Join(err, e)
+ } else {
+ reports = append(reports, report)
+ }
+ }
+ if err != nil {
+ return
+ }
+ combined, err := sarifutils.CombineReports(reports...)
+ if err != nil {
+ return
+ }
+ return JSONMarshalNotEscaped(combined)
+}
+
+func loadSarifReport(dataFilePath string) (report *sarif.Report, err error) {
+ fileData, err := os.ReadFile(dataFilePath)
+ if err != nil {
+ return
+ }
+ return sarif.FromBytes(fileData)
+}
+
func updateSummaryNamesToRelativePath(summary *formats.ResultsSummary, wd string) {
for i, scan := range summary.Scans {
if scan.Target == "" {
@@ -184,15 +240,15 @@ func updateSummaryNamesToRelativePath(summary *formats.ResultsSummary, wd string
}
}
-func getDataIndexFromSection(section SecuritySummarySection) commandsummary.Index {
- switch section {
+func getDataIndexFromCommandType(cmdType CommandType) commandsummary.Index {
+ switch cmdType {
case Build:
return commandsummary.BuildScan
case Binary:
return commandsummary.BinariesScan
- case Modules:
+ case SourceCode:
return commandsummary.BinariesScan
- case Docker:
+ case DockerImage:
return commandsummary.DockerScan
}
// No index for the section
@@ -213,11 +269,11 @@ func recordIndexData(manager *commandsummary.CommandSummary, content ScanCommand
return
}
-func newScanCommandResultSummary(resultType SecuritySummarySection, args *ResultSummaryArgs, scans ...formats.ScanSummary) ScanCommandResultSummary {
+func newScanCommandResultSummary(resultType CommandType, args *ResultSummaryArgs, scans ...formats.ScanSummary) ScanCommandResultSummary {
return ScanCommandResultSummary{ResultType: resultType, Args: args, Summary: formats.ResultsSummary{Scans: scans}}
}
-func loadContent(dataFiles []string, filterSections ...SecuritySummarySection) ([]formats.ResultsSummary, ResultSummaryArgs, error) {
+func loadContent(dataFiles []string, filterSections ...CommandType) ([]formats.ResultsSummary, ResultSummaryArgs, error) {
data := []formats.ResultsSummary{}
args := ResultSummaryArgs{}
for _, dataFilePath := range dataFiles {
@@ -282,7 +338,7 @@ func GenerateSecuritySectionMarkdown(curationData []formats.ResultsSummary) (mar
return
}
// Create the markdown content
- markdown += fmt.Sprintf("\n\n#### %s\n| Audit Summary | Project name | Audit Details |\n|--------|--------|---------|", Curation)
+ markdown += "\n\n| Audit Summary | Project name | Audit Details |\n|--------|--------|---------|"
for i := range curationData {
for _, summary := range curationData[i].Scans {
status := getStatusIcon(false)
@@ -292,7 +348,7 @@ func GenerateSecuritySectionMarkdown(curationData []formats.ResultsSummary) (mar
markdown += fmt.Sprintf("\n| %s | %s | %s |", status, summary.Target, PreFormat.Format(getCurationDetailsString(summary)))
}
}
- markdown = DetailsOpenWithSummary.Format("🔒 Security Summary", markdown)
+ markdown = "\n" + DetailsOpenWithSummary.Format("🔒 Curation Audit", markdown)
return
}
@@ -327,7 +383,7 @@ func getCurationDetailsString(summary formats.ScanSummary) (content string) {
var blocked []blockedPackageByType
// Sort the blocked packages by name
for _, blockTypeValue := range summary.CuratedPackages.Blocked {
- blocked = append(blocked, toBlockedPackgeByType(blockTypeValue))
+ blocked = append(blocked, toBlockedPackageByType(blockTypeValue))
}
sort.Slice(blocked, func(i, j int) bool {
return blocked[i].BlockedType > blocked[j].BlockedType
@@ -342,7 +398,7 @@ func getCurationDetailsString(summary formats.ScanSummary) (content string) {
return
}
-func toBlockedPackgeByType(blockTypeValue formats.BlockedPackages) blockedPackageByType {
+func toBlockedPackageByType(blockTypeValue formats.BlockedPackages) blockedPackageByType {
return blockedPackageByType{BlockedType: formatPolicyAndCond(blockTypeValue.Policy, blockTypeValue.Condition), BlockedSummary: blockTypeValue.Packages}
}
@@ -563,8 +619,8 @@ func getSeverityStatusesCountString(statusCounts map[string]int) string {
return generateSeverityStatusesCountString(getSeverityDisplayStatuses(statusCounts))
}
-func getSeverityDisplayStatuses(statusCounts map[string]int) (displayData map[SeverityStatus]int) {
- displayData = map[SeverityStatus]int{}
+func getSeverityDisplayStatuses(statusCounts map[string]int) (displayData map[SeverityDisplayStatus]int) {
+ displayData = map[SeverityDisplayStatus]int{}
for status, count := range statusCounts {
switch status {
case jasutils.Applicability.String():
@@ -576,7 +632,7 @@ func getSeverityDisplayStatuses(statusCounts map[string]int) (displayData map[Se
return displayData
}
-func generateSeverityStatusesCountString(displayData map[SeverityStatus]int) string {
+func generateSeverityStatusesCountString(displayData map[SeverityDisplayStatus]int) string {
if len(displayData) == 0 {
return ""
}
diff --git a/utils/securityJobSummary_test.go b/utils/securityJobSummary_test.go
index e447a7fb..d5890907 100644
--- a/utils/securityJobSummary_test.go
+++ b/utils/securityJobSummary_test.go
@@ -39,7 +39,7 @@ var (
func TestSaveLoadData(t *testing.T) {
testDockerScanSummary := ScanCommandResultSummary{
- ResultType: Docker,
+ ResultType: DockerImage,
Args: &ResultSummaryArgs{
BaseJfrogUrl: testPlatformUrl,
DockerImage: "dockerImage:version",
@@ -133,7 +133,7 @@ func TestSaveLoadData(t *testing.T) {
testCases := []struct {
name string
content []ScanCommandResultSummary
- filterSections []SecuritySummarySection
+ filterSections []CommandType
expectedArgs ResultSummaryArgs
expectedContent []formats.ResultsSummary
}{
@@ -156,7 +156,7 @@ func TestSaveLoadData(t *testing.T) {
},
{
name: "Multiple scans with filter",
- filterSections: []SecuritySummarySection{Curation},
+ filterSections: []CommandType{Curation},
content: []ScanCommandResultSummary{testDockerScanSummary, testBinaryScanSummary, testBuildScanSummary, testCurationSummary},
expectedContent: []formats.ResultsSummary{testCurationSummary.Summary},
},
@@ -169,7 +169,7 @@ func TestSaveLoadData(t *testing.T) {
// Save the data
for i := range testCase.content {
updateSummaryNamesToRelativePath(&testCase.content[i].Summary, tempDir)
- data, err := JSONMarshal(&testCase.content[i])
+ data, err := JSONMarshalNotEscaped(&testCase.content[i])
assert.NoError(t, err)
dataFilePath := filepath.Join(tempDir, fmt.Sprintf("data_%s_%d.json", testCase.name, i))
assert.NoError(t, os.WriteFile(dataFilePath, data, 0644))
diff --git a/utils/utils.go b/utils/utils.go
index b4aa871e..1b4d2da7 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -1,6 +1,8 @@
package utils
import (
+ "crypto"
+ "encoding/hex"
"fmt"
"strings"
)
@@ -38,14 +40,48 @@ func (v ViolationIssueType) String() string {
type SubScanType string
+const (
+ SourceCode CommandType = "source_code"
+ Binary CommandType = "binary"
+ DockerImage CommandType = "docker_image"
+ Build CommandType = "build"
+ Curation CommandType = "curation"
+ SBOM CommandType = "SBOM"
+)
+
+type CommandType string
+
func (s SubScanType) String() string {
return string(s)
}
+func (s CommandType) IsTargetBinary() bool {
+ return s == Binary || s == DockerImage
+}
+
func GetAllSupportedScans() []SubScanType {
return []SubScanType{ScaScan, ContextualAnalysisScan, IacScan, SastScan, SecretsScan}
}
+func Md5Hash(values ...string) (string, error) {
+ return toHash(crypto.MD5, values...)
+}
+
+func Sha1Hash(values ...string) (string, error) {
+ return toHash(crypto.SHA1, values...)
+}
+
+func toHash(hash crypto.Hash, values ...string) (string, error) {
+ h := hash.New()
+ for _, ob := range values {
+ _, err := fmt.Fprint(h, ob)
+ if err != nil {
+ return "", err
+ }
+ }
+ return hex.EncodeToString(h.Sum(nil)), nil
+}
+
// map[string]string to []string (key=value format)
func ToCommandEnvVars(envVarsMap map[string]string) (converted []string) {
converted = make([]string, 0, len(envVarsMap))