Skip to content

Commit

Permalink
Audit - Sarif output, show default location for license violation (#1030
Browse files Browse the repository at this point in the history
)
  • Loading branch information
attiasas authored Nov 16, 2023
1 parent d8059b5 commit b0db3e2
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 27 deletions.
1 change: 1 addition & 0 deletions xray/utils/resultstable.go
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,7 @@ func simplifyViolations(scanViolations []services.Violation, multipleRoots bool)
continue
}
uniqueViolations[packageKey] = &services.Violation{
Summary: violation.Summary,
Severity: violation.Severity,
ViolationType: violation.ViolationType,
Components: map[string]services.Component{vulnerableComponentId: violation.Components[vulnerableComponentId]},
Expand Down
78 changes: 51 additions & 27 deletions xray/utils/resultwriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/jfrog/jfrog-client-go/utils/log"
"github.com/jfrog/jfrog-client-go/xray/services"
"github.com/owenrumney/go-sarif/v2/sarif"
"golang.org/x/exp/slices"
)

type OutputFormat string
Expand Down Expand Up @@ -117,15 +118,7 @@ func (rw *ResultsWriter) PrintScanResults() error {
case Json:
return PrintJson(rw.results.GetScaScansXrayResults())
case Sarif:
sarifReport, err := GenereateSarifReportFromResults(rw.results, rw.isMultipleRoots, rw.includeLicenses)
if err != nil {
return err
}
sarifFile, err := ConvertSarifReportToString(sarifReport)
if err != nil {
return err
}
log.Output(sarifFile)
return PrintSarif(rw.results, rw.isMultipleRoots, rw.includeLicenses)
}
return nil
}
Expand Down Expand Up @@ -175,12 +168,12 @@ func printMessage(message string) {
log.Output("💬" + message)
}

func GenereateSarifReportFromResults(results *Results, isMultipleRoots, includeLicenses bool) (report *sarif.Report, err error) {
func GenereateSarifReportFromResults(results *Results, isMultipleRoots, includeLicenses bool, allowedLicenses []string) (report *sarif.Report, err error) {
report, err = NewReport()
if err != nil {
return
}
xrayRun, err := convertXrayResponsesToSarifRun(results, isMultipleRoots, includeLicenses)
xrayRun, err := convertXrayResponsesToSarifRun(results, isMultipleRoots, includeLicenses, allowedLicenses)
if err != nil {
return
}
Expand All @@ -202,14 +195,14 @@ func ConvertSarifReportToString(report *sarif.Report) (sarifStr string, err erro
return clientUtils.IndentJson(out), nil
}

func convertXrayResponsesToSarifRun(results *Results, isMultipleRoots, includeLicenses bool) (run *sarif.Run, err error) {
xrayJson, err := convertXrayScanToSimpleJson(results, isMultipleRoots, includeLicenses, true)
func convertXrayResponsesToSarifRun(results *Results, isMultipleRoots, includeLicenses bool, allowedLicenses []string) (run *sarif.Run, err error) {
xrayJson, err := ConvertXrayScanToSimpleJson(results, isMultipleRoots, includeLicenses, true, allowedLicenses)
if err != nil {
return
}
xrayRun := sarif.NewRunWithInformationURI("JFrog Xray SCA", BaseDocumentationURL+"sca")
xrayRun.Tool.Driver.Version = &results.XrayVersion
if len(xrayJson.Vulnerabilities) > 0 || len(xrayJson.SecurityViolations) > 0 {
if len(xrayJson.Vulnerabilities) > 0 || len(xrayJson.SecurityViolations) > 0 || len(xrayJson.LicensesViolations) > 0 {
if err = extractXrayIssuesToSarifRun(xrayRun, xrayJson); err != nil {
return
}
Expand Down Expand Up @@ -283,7 +276,7 @@ func addXrayLicenseViolationToSarifRun(license formats.LicenseRow, run *sarif.Ru
getXrayLicenseSarifHeadline(license.ImpactedDependencyName, license.ImpactedDependencyVersion, license.LicenseKey),
getLicenseViolationMarkdown(license.ImpactedDependencyName, license.ImpactedDependencyVersion, license.LicenseKey, formattedDirectDependencies),
license.Components,
nil,
getXrayIssueLocation(""),
run,
)
return
Expand Down Expand Up @@ -329,10 +322,14 @@ func getXrayIssueLocationIfValidExists(tech coreutils.Technology, run *sarif.Run
if err != nil {
return
}
if strings.TrimSpace(descriptorPath) == "" {
descriptorPath = "Package-Descriptor"
return getXrayIssueLocation(descriptorPath), nil
}

func getXrayIssueLocation(filePath string) *sarif.Location {
if strings.TrimSpace(filePath) == "" {
filePath = "Package-Descriptor"
}
return sarif.NewLocation().WithPhysicalLocation(sarif.NewPhysicalLocation().WithArtifactLocation(sarif.NewArtifactLocation().WithUri("file://" + descriptorPath))), nil
return sarif.NewLocation().WithPhysicalLocation(sarif.NewPhysicalLocation().WithArtifactLocation(sarif.NewArtifactLocation().WithUri("file://" + filePath)))
}

func addXrayRule(ruleId, ruleDescription, maxCveScore, summary, markdownDescription string, run *sarif.Run) {
Expand All @@ -351,7 +348,7 @@ func addXrayRule(ruleId, ruleDescription, maxCveScore, summary, markdownDescript
})
}

func convertXrayScanToSimpleJson(results *Results, isMultipleRoots, includeLicenses, simplifiedOutput bool) (formats.SimpleJsonResults, error) {
func ConvertXrayScanToSimpleJson(results *Results, isMultipleRoots, includeLicenses, simplifiedOutput bool, allowedLicenses []string) (formats.SimpleJsonResults, error) {
violations, vulnerabilities, licenses := SplitScanResults(results.ScaResults)
jsonTable := formats.SimpleJsonResults{}
if len(vulnerabilities) > 0 {
Expand All @@ -361,6 +358,16 @@ func convertXrayScanToSimpleJson(results *Results, isMultipleRoots, includeLicen
}
jsonTable.Vulnerabilities = vulJsonTable
}
if includeLicenses || len(allowedLicenses) > 0 {
licJsonTable, err := PrepareLicenses(licenses)
if err != nil {
return formats.SimpleJsonResults{}, err
}
if includeLicenses {
jsonTable.Licenses = licJsonTable
}
jsonTable.LicensesViolations = GetViolatedLicenses(allowedLicenses, licJsonTable)
}
if len(violations) > 0 {
secViolationsJsonTable, licViolationsJsonTable, opRiskViolationsJsonTable, err := PrepareViolations(violations, results, isMultipleRoots, simplifiedOutput)
if err != nil {
Expand All @@ -370,19 +377,23 @@ func convertXrayScanToSimpleJson(results *Results, isMultipleRoots, includeLicen
jsonTable.LicensesViolations = licViolationsJsonTable
jsonTable.OperationalRiskViolations = opRiskViolationsJsonTable
}
if includeLicenses {
licJsonTable, err := PrepareLicenses(licenses)
if err != nil {
return formats.SimpleJsonResults{}, err
return jsonTable, nil
}

func GetViolatedLicenses(allowedLicenses []string, licenses []formats.LicenseRow) (violatedLicenses []formats.LicenseRow) {
if len(allowedLicenses) == 0 {
return
}
for _, license := range licenses {
if !slices.Contains(allowedLicenses, license.LicenseKey) {
violatedLicenses = append(violatedLicenses, license)
}
jsonTable.Licenses = licJsonTable
}

return jsonTable, nil
return
}

func (rw *ResultsWriter) convertScanToSimpleJson() (formats.SimpleJsonResults, error) {
jsonTable, err := convertXrayScanToSimpleJson(rw.results, rw.isMultipleRoots, rw.includeLicenses, false)
jsonTable, err := ConvertXrayScanToSimpleJson(rw.results, rw.isMultipleRoots, rw.includeLicenses, false, nil)
if err != nil {
return formats.SimpleJsonResults{}, err
}
Expand Down Expand Up @@ -534,6 +545,19 @@ func PrintJson(output interface{}) error {
return nil
}

func PrintSarif(results *Results, isMultipleRoots, includeLicenses bool) error {
sarifReport, err := GenereateSarifReportFromResults(results, isMultipleRoots, includeLicenses, nil)
if err != nil {
return err
}
sarifFile, err := ConvertSarifReportToString(sarifReport)
if err != nil {
return err
}
log.Output(sarifFile)
return nil
}

func CheckIfFailBuild(results []services.ScanResponse) bool {
for _, result := range results {
for _, violation := range result.Violations {
Expand Down
184 changes: 184 additions & 0 deletions xray/utils/resultwriter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
"github.com/jfrog/jfrog-cli-core/v2/utils/tests"
"github.com/jfrog/jfrog-cli-core/v2/xray/formats"
"github.com/jfrog/jfrog-client-go/xray/services"
"github.com/owenrumney/go-sarif/v2/sarif"
"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -193,3 +194,186 @@ func TestGetXrayIssueLocationIfValidExists(t *testing.T) {
})
}
}

func TestConvertXrayScanToSimpleJson(t *testing.T) {
vulnerabilities := []services.Vulnerability{
{
IssueId: "XRAY-1",
Summary: "summary-1",
Severity: "high",
Components: map[string]services.Component{"component-A": {}, "component-B": {}},
},
{
IssueId: "XRAY-2",
Summary: "summary-2",
Severity: "low",
Components: map[string]services.Component{"component-B": {}},
},
}
expectedVulnerabilities := []formats.VulnerabilityOrViolationRow{
{
Summary: "summary-1",
IssueId: "XRAY-1",
ImpactedDependencyDetails: formats.ImpactedDependencyDetails{
SeverityDetails: formats.SeverityDetails{Severity: "high"},
ImpactedDependencyName: "component-A",
},
},
{
Summary: "summary-1",
IssueId: "XRAY-1",
ImpactedDependencyDetails: formats.ImpactedDependencyDetails{
SeverityDetails: formats.SeverityDetails{Severity: "high"},
ImpactedDependencyName: "component-B",
},
},
{
Summary: "summary-2",
IssueId: "XRAY-2",
ImpactedDependencyDetails: formats.ImpactedDependencyDetails{
SeverityDetails: formats.SeverityDetails{Severity: "low"},
ImpactedDependencyName: "component-B",
},
},
}

violations := []services.Violation{
{
IssueId: "XRAY-1",
Summary: "summary-1",
Severity: "high",
WatchName: "watch-1",
ViolationType: "security",
Components: map[string]services.Component{"component-A": {}, "component-B": {}},
},
{
IssueId: "XRAY-2",
Summary: "summary-2",
Severity: "low",
WatchName: "watch-1",
ViolationType: "license",
LicenseKey: "license-1",
Components: map[string]services.Component{"component-B": {}},
},
}
expectedSecViolations := []formats.VulnerabilityOrViolationRow{
{
Summary: "summary-1",
IssueId: "XRAY-1",
ImpactedDependencyDetails: formats.ImpactedDependencyDetails{
SeverityDetails: formats.SeverityDetails{Severity: "high"},
ImpactedDependencyName: "component-A",
},
},
{
Summary: "summary-1",
IssueId: "XRAY-1",
ImpactedDependencyDetails: formats.ImpactedDependencyDetails{
SeverityDetails: formats.SeverityDetails{Severity: "high"},
ImpactedDependencyName: "component-B",
},
},
}
expectedLicViolations := []formats.LicenseRow{
{
LicenseKey: "license-1",
ImpactedDependencyDetails: formats.ImpactedDependencyDetails{
SeverityDetails: formats.SeverityDetails{Severity: "low"},
ImpactedDependencyName: "component-B",
},
},
}

licenses := []services.License{
{
Key: "license-1",
Name: "license-1-name",
Components: map[string]services.Component{"component-A": {}, "component-B": {}},
},
{
Key: "license-2",
Name: "license-2-name",
Components: map[string]services.Component{"component-B": {}},
},
}
expectedLicenses := []formats.LicenseRow{
{
LicenseKey: "license-1",
ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "component-A"},
},
{
LicenseKey: "license-1",
ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "component-B"},
},
{
LicenseKey: "license-2",
ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "component-B"},
},
}

testCases := []struct {
name string
result services.ScanResponse
includeLicenses bool
allowedLicenses []string
expectedOutput formats.SimpleJsonResults
}{
{
name: "Vulnerabilities only",
includeLicenses: false,
allowedLicenses: nil,
result: services.ScanResponse{Vulnerabilities: vulnerabilities, Licenses: licenses},
expectedOutput: formats.SimpleJsonResults{Vulnerabilities: expectedVulnerabilities},
},
{
name: "Vulnerabilities with licenses",
includeLicenses: true,
allowedLicenses: nil,
result: services.ScanResponse{Vulnerabilities: vulnerabilities, Licenses: licenses},
expectedOutput: formats.SimpleJsonResults{Vulnerabilities: expectedVulnerabilities, Licenses: expectedLicenses},
},
{
name: "Vulnerabilities only - with allowed licenses",
includeLicenses: false,
allowedLicenses: []string{"license-1"},
result: services.ScanResponse{Vulnerabilities: vulnerabilities, Licenses: licenses},
expectedOutput: formats.SimpleJsonResults{
Vulnerabilities: expectedVulnerabilities,
LicensesViolations: []formats.LicenseRow{
{
LicenseKey: "license-2",
ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ImpactedDependencyName: "component-B"},
},
},
},
},
{
name: "Violations only",
includeLicenses: false,
allowedLicenses: nil,
result: services.ScanResponse{Violations: violations, Licenses: licenses},
expectedOutput: formats.SimpleJsonResults{SecurityViolations: expectedSecViolations, LicensesViolations: expectedLicViolations},
},
{
name: "Violations - override allowed licenses",
includeLicenses: false,
allowedLicenses: []string{"license-1"},
result: services.ScanResponse{Violations: violations, Licenses: licenses},
expectedOutput: formats.SimpleJsonResults{SecurityViolations: expectedSecViolations, LicensesViolations: expectedLicViolations},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
results := NewAuditResults()
results.ScaResults = append(results.ScaResults, ScaScanResult{XrayResults: []services.ScanResponse{tc.result}})
output, err := ConvertXrayScanToSimpleJson(results, false, tc.includeLicenses, true, tc.allowedLicenses)
if assert.NoError(t, err) {
assert.ElementsMatch(t, tc.expectedOutput.Vulnerabilities, output.Vulnerabilities)
assert.ElementsMatch(t, tc.expectedOutput.SecurityViolations, output.SecurityViolations)
assert.ElementsMatch(t, tc.expectedOutput.LicensesViolations, output.LicensesViolations)
assert.ElementsMatch(t, tc.expectedOutput.Licenses, output.Licenses)
assert.ElementsMatch(t, tc.expectedOutput.OperationalRiskViolations, output.OperationalRiskViolations)
}
})
}
}

0 comments on commit b0db3e2

Please sign in to comment.