Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Sarif output driver issues and Xray Sca locations #968

Merged
merged 34 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
de990d2
Fix Sarif output driver issues and Xray Sca locations
attiasas Sep 19, 2023
53e747f
Fix Sarif output driver issues
attiasas Sep 19, 2023
2ecc294
Fix bug
attiasas Sep 19, 2023
d26ce78
rename SCA
attiasas Sep 19, 2023
f97e80c
merge dev
attiasas Sep 19, 2023
4cac214
add specific uri
attiasas Sep 20, 2023
d6632a2
merge dev
attiasas Sep 20, 2023
79037ef
reposition override
attiasas Sep 20, 2023
46ad98f
reposition override
attiasas Sep 20, 2023
954d9c9
Merge remote-tracking branch 'upstream/dev' into fix_xray_sarif
attiasas Sep 26, 2023
1d97508
add test
attiasas Sep 26, 2023
f50acfb
update information uri
attiasas Sep 26, 2023
c6f29dd
split sarifutils and move test funcs
attiasas Sep 26, 2023
cb563be
fix for windows
attiasas Sep 26, 2023
6065ccf
Remove Sast support check
attiasas Sep 26, 2023
0782ab9
review changes
attiasas Sep 26, 2023
11049cb
fix tests
attiasas Sep 26, 2023
57e71bd
fix tests
attiasas Sep 26, 2023
a49e5a4
Merge remote-tracking branch 'upstream/dev' into fix_xray_sarif
attiasas Sep 27, 2023
df406b2
Merge remote-tracking branch 'upstream/dev' into fix_xray_sarif
attiasas Oct 1, 2023
df8398f
fix review
attiasas Oct 2, 2023
7d209d5
Merge remote-tracking branch 'upstream/dev' into fix_xray_sarif
attiasas Oct 2, 2023
a2deb81
format
attiasas Oct 2, 2023
998e7ac
fix test
attiasas Oct 2, 2023
0b5f0c4
fix tests
attiasas Oct 2, 2023
7d4a59b
remove trim
attiasas Oct 2, 2023
2ebdb0c
review changes
attiasas Oct 2, 2023
9ff4e71
Merge remote-tracking branch 'upstream/dev' into fix_xray_sarif
attiasas Oct 2, 2023
15ad8d2
done changes
attiasas Oct 3, 2023
5c7d21a
format
attiasas Oct 3, 2023
d2e55bc
fix tests
attiasas Oct 3, 2023
2aed015
Merge remote-tracking branch 'upstream/dev' into fix_xray_sarif
attiasas Oct 3, 2023
306e9f3
cleanup
attiasas Oct 3, 2023
719a5e8
add test
attiasas Oct 3, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 60 additions & 10 deletions xray/utils/resultwriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ import (
"bytes"
"encoding/json"
"fmt"
"strconv"
"strings"

"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
"github.com/jfrog/jfrog-cli-core/v2/xray/formats"
clientUtils "github.com/jfrog/jfrog-client-go/utils"
Expand All @@ -15,6 +12,9 @@ import (
"github.com/jfrog/jfrog-client-go/utils/log"
"github.com/jfrog/jfrog-client-go/xray/services"
"github.com/owenrumney/go-sarif/v2/sarif"
"strconv"
"strings"
"unicode"
)

type OutputFormat string
Expand Down Expand Up @@ -128,10 +128,10 @@ func GenerateSarifContentFromResults(extendedResults *ExtendedScanResults, isMul
}

report.Runs = append(report.Runs, xrayRun)
report.Runs = append(report.Runs, extendedResults.ApplicabilityScanResults...)
report.Runs = append(report.Runs, extendedResults.IacScanResults...)
report.Runs = append(report.Runs, extendedResults.SecretsScanResults...)
report.Runs = append(report.Runs, extendedResults.SastScanResults...)
report.Runs = append(report.Runs, fillMissingRequiredInformationForJas("https://docs.jfrog-applications.jfrog.io/jfrog-security-features/contextual-analysis", extendedResults.ApplicabilityScanResults)...)
attiasas marked this conversation as resolved.
Show resolved Hide resolved
report.Runs = append(report.Runs, fillMissingRequiredInformationForJas("https://docs.jfrog-applications.jfrog.io/jfrog-security-features/infrastructure-as-code-iac", extendedResults.IacScanResults)...)
report.Runs = append(report.Runs, fillMissingRequiredInformationForJas("https://docs.jfrog-applications.jfrog.io/jfrog-security-features/secrets", extendedResults.SecretsScanResults)...)
report.Runs = append(report.Runs, fillMissingRequiredInformationForJas("https://docs.jfrog-applications.jfrog.io/jfrog-security-features/sast", extendedResults.SastScanResults)...)

out, err := json.Marshal(report)
if err != nil {
Expand All @@ -141,12 +141,34 @@ func GenerateSarifContentFromResults(extendedResults *ExtendedScanResults, isMul
return clientUtils.IndentJson(out), nil
}

func fillMissingRequiredInformationForJas(defaultJasInformationUri string, runs []*sarif.Run) []*sarif.Run {
defaultVersion := GetAnalyzerManagerVersion()
for _, run := range runs {
driver := run.Tool.Driver
if driver.InformationURI == nil {
driver.InformationURI = &defaultJasInformationUri
}
if driver.Version == nil || !isValidVersion(*driver.Version) {
driver.Version = &defaultVersion
}
}
return runs
}

func isValidVersion(version string) bool {
if len(version) == 0 {
return false
}
firstChar := rune(version[0])
return unicode.IsDigit(firstChar)
}

func convertXrayResponsesToSarifRun(extendedResults *ExtendedScanResults, isMultipleRoots, includeLicenses, markdownOutput bool) (run *sarif.Run, err error) {
xrayJson, err := convertXrayScanToSimpleJson(extendedResults, isMultipleRoots, includeLicenses, true)
if err != nil {
return
}
xrayRun := sarif.NewRunWithInformationURI("JFrog Xray Sca", "https://jfrog.com/xray/")
xrayRun := sarif.NewRunWithInformationURI("JFrog Xray SCA", "https://docs.jfrog-applications.jfrog.io/jfrog-security-features/sca")
xrayRun.Tool.Driver.Version = &extendedResults.XrayVersion
if len(xrayJson.Vulnerabilities) > 0 || len(xrayJson.SecurityViolations) > 0 {
if err = extractXrayIssuesToSarifRun(xrayRun, xrayJson, markdownOutput); err != nil {
Expand Down Expand Up @@ -210,8 +232,10 @@ func addXrayCveIssueToSarifRun(cves []formats.CveRow, issueId, severity, file st
}
cveId := GetIssueIdentifier(cves, issueId)
msg := getVulnerabilityOrViolationSarifHeadline(impactedDependencyName, impactedDependencyVersion, cveId)
location := sarif.NewLocation().WithPhysicalLocation(sarif.NewPhysicalLocation().WithArtifactLocation(sarif.NewArtifactLocation().WithUri(file)))

location, err := getXrayIssueLocationIfValidExists(file, run, markdownOutput)
if err != nil {
return err
}
if rule, isNewRule := addResultToSarifRun(cveId, msg, severity, location, run); isNewRule {
cveRuleProperties := sarif.NewPropertyBag()
if maxCveScore != MissingCveScore {
Expand All @@ -232,6 +256,32 @@ func addXrayCveIssueToSarifRun(cves []formats.CveRow, issueId, severity, file st
return nil
}

// Xray GetPackageDescriptor can return multiple types of content.
// This could cause the sarif content not to be valid. if not override, we should handle all those situations:
// Full path - should be used as is
// Relative path - should be converted to full path
// Non path - should not be used as location
func getXrayIssueLocationIfValidExists(file string, run *sarif.Run, override bool) (location *sarif.Location, err error) {
location = sarif.NewLocation().WithPhysicalLocation(sarif.NewPhysicalLocation().WithArtifactLocation(sarif.NewArtifactLocation().WithUri(file)))
if override {
// Use the content as is
return
}
// Check if full path
exists, err := fileutils.IsFileExists(file, false)
if err != nil || exists {
return
}
// Check if relative path
fullPath := GetFullLocationFileName(file, run.Invocations)
location = sarif.NewLocation().WithPhysicalLocation(sarif.NewPhysicalLocation().WithArtifactLocation(sarif.NewArtifactLocation().WithUri("file://" + fullPath)))
if exists, err = fileutils.IsFileExists(fullPath, false); err != nil || exists {
return
}
// Not usable content
return nil, nil
}

func addResultToSarifRun(issueId, msg, severity string, location *sarif.Location, run *sarif.Run) (rule *sarif.ReportingDescriptor, isNewRule bool) {
if rule, _ = run.GetRuleById(issueId); rule == nil {
isNewRule = true
Expand Down
69 changes: 7 additions & 62 deletions xray/utils/sarifutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,14 +147,20 @@ func GetRelativeLocationFileName(location *sarif.Location, invocations []*sarif.
if len(invocations) > 0 {
wd = GetInvocationWorkingDirectory(invocations[0])
}
GetLocationFileName(location)
filePath := GetLocationFileName(location)
if filePath != "" {
return ExtractRelativePath(filePath, wd)
}
return ""
}

func GetFullLocationFileName(relative string, invocations []*sarif.Invocation) string {
if len(invocations) == 0 {
return relative
}
return filepath.Join(GetInvocationWorkingDirectory(invocations[0]), relative)
}

func SetLocationFileName(location *sarif.Location, fileName string) {
if location != nil && location.PhysicalLocation != nil && location.PhysicalLocation.Region != nil && location.PhysicalLocation.Region.Snippet != nil {
location.PhysicalLocation.ArtifactLocation.URI = &fileName
Expand Down Expand Up @@ -259,64 +265,3 @@ func GetInvocationWorkingDirectory(invocation *sarif.Invocation) string {
}
return ""
}

func CreateRunWithDummyResults(results ...*sarif.Result) *sarif.Run {
run := sarif.NewRunWithInformationURI("", "")
for _, result := range results {
if result.RuleID != nil {
run.AddRule(*result.RuleID)
}
run.AddResult(result)
}
return run
}

func CreateResultWithLocations(msg, ruleId, level string, locations ...*sarif.Location) *sarif.Result {
return &sarif.Result{
Message: *sarif.NewTextMessage(msg),
Locations: locations,
Level: &level,
RuleID: &ruleId,
}
}

func CreateLocation(fileName string, startLine, startCol, endLine, endCol int, snippet string) *sarif.Location {
return &sarif.Location{
PhysicalLocation: &sarif.PhysicalLocation{
ArtifactLocation: &sarif.ArtifactLocation{URI: &fileName},
Region: &sarif.Region{
StartLine: &startLine,
StartColumn: &startCol,
EndLine: &endLine,
EndColumn: &endCol,
Snippet: &sarif.ArtifactContent{Text: &snippet}}},
}
}

func CreateDummyPassingResult(ruleId string) *sarif.Result {
kind := "pass"
return &sarif.Result{
Kind: &kind,
RuleID: &ruleId,
}
}

func CreateResultWithOneLocation(fileName string, startLine, startCol, endLine, endCol int, snippet, ruleId, level string) *sarif.Result {
return CreateResultWithLocations("", ruleId, level, CreateLocation(fileName, startLine, startCol, endLine, endCol, snippet))
}

func CreateCodeFlow(threadFlows ...*sarif.ThreadFlow) *sarif.CodeFlow {
flow := sarif.NewCodeFlow()
for _, threadFlow := range threadFlows {
flow.AddThreadFlow(threadFlow)
}
return flow
}

func CreateThreadFlow(locations ...*sarif.Location) *sarif.ThreadFlow {
stackStrace := sarif.NewThreadFlow()
for _, location := range locations {
stackStrace.AddLocation(sarif.NewThreadFlowLocation().WithLocation(location))
}
return stackStrace
}
31 changes: 31 additions & 0 deletions xray/utils/sarifutils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,37 @@ func TestGetRelativeLocationFileName(t *testing.T) {
}
}

func TestGetFullLocationFileName(t *testing.T) {
tests := []struct {
file string
invocations []*sarif.Invocation
expectedOutput string
}{
{
file: "root/someDir/another/file",
invocations: []*sarif.Invocation{},
expectedOutput: "root/someDir/another/file",
},
{
file: "/another/file",
invocations: []*sarif.Invocation{
{WorkingDirectory: sarif.NewSimpleArtifactLocation("/root/someDir/")},
{WorkingDirectory: sarif.NewSimpleArtifactLocation("/not/relevant")},
},
expectedOutput: "/root/someDir/another/file",
},
{
file: "another/file",
invocations: []*sarif.Invocation{{WorkingDirectory: sarif.NewSimpleArtifactLocation("/root/someDir")}},
expectedOutput: "/root/someDir/another/file",
},
}

for _, test := range tests {
assert.Equal(t, test.expectedOutput, GetFullLocationFileName(test.file, test.invocations))
}
}

func TestSetLocationFileName(t *testing.T) {
tests := []struct {
location *sarif.Location
Expand Down
64 changes: 64 additions & 0 deletions xray/utils/test_sarifutils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package utils

import "github.com/owenrumney/go-sarif/v2/sarif"

func CreateRunWithDummyResults(results ...*sarif.Result) *sarif.Run {
run := sarif.NewRunWithInformationURI("", "")
for _, result := range results {
if result.RuleID != nil {
run.AddRule(*result.RuleID)
}
run.AddResult(result)
}
return run
}

func CreateResultWithLocations(msg, ruleId, level string, locations ...*sarif.Location) *sarif.Result {
return &sarif.Result{
Message: *sarif.NewTextMessage(msg),
Locations: locations,
Level: &level,
RuleID: &ruleId,
}
}

func CreateLocation(fileName string, startLine, startCol, endLine, endCol int, snippet string) *sarif.Location {
return &sarif.Location{
PhysicalLocation: &sarif.PhysicalLocation{
ArtifactLocation: &sarif.ArtifactLocation{URI: &fileName},
Region: &sarif.Region{
StartLine: &startLine,
StartColumn: &startCol,
EndLine: &endLine,
EndColumn: &endCol,
Snippet: &sarif.ArtifactContent{Text: &snippet}}},
}
}

func CreateDummyPassingResult(ruleId string) *sarif.Result {
kind := "pass"
return &sarif.Result{
Kind: &kind,
RuleID: &ruleId,
}
}

func CreateResultWithOneLocation(fileName string, startLine, startCol, endLine, endCol int, snippet, ruleId, level string) *sarif.Result {
return CreateResultWithLocations("", ruleId, level, CreateLocation(fileName, startLine, startCol, endLine, endCol, snippet))
}

func CreateCodeFlow(threadFlows ...*sarif.ThreadFlow) *sarif.CodeFlow {
flow := sarif.NewCodeFlow()
for _, threadFlow := range threadFlows {
flow.AddThreadFlow(threadFlow)
}
return flow
}

func CreateThreadFlow(locations ...*sarif.Location) *sarif.ThreadFlow {
stackStrace := sarif.NewThreadFlow()
for _, location := range locations {
stackStrace.AddLocation(sarif.NewThreadFlowLocation().WithLocation(location))
}
return stackStrace
}
Loading