sarif: Make sarif easier to use with a default template

This will help us use Trivy in places like GitHub Actions where
we cannot specify a template as input.

$ trivy image --format=sarif alpine:3.10.1

Signed-off-by: Simarpreet Singh <simar@linux.com>
This commit is contained in:
Simarpreet Singh
2020-07-24 13:55:07 -07:00
parent c3045f99c0
commit 5b5d1c8f7d
4 changed files with 290 additions and 2 deletions

View File

@@ -899,6 +899,11 @@ In the following example using the template `sarif.tpl` [Sarif](https://docs.git
$ trivy image --format template --template "@contrib/sarif.tpl" -o report.sarif golang:1.12-alpine
```
You can also use the default SARIF format that's included with Trivy as follows
```
$ trivy image --format=sarif golang:1.12-alpine
```
### Filter the vulnerabilities by severities
```

View File

@@ -38,7 +38,7 @@ var (
Name: "format",
Aliases: []string{"f"},
Value: "table",
Usage: "format (table, json, template)",
Usage: "format (table, json, sarif, template)",
EnvVars: []string{"TRIVY_FORMAT"},
}

View File

@@ -4,9 +4,11 @@ import (
"bytes"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"strings"
"text/template"
@@ -19,6 +21,10 @@ import (
"github.com/olekukonko/tablewriter"
)
var (
DefaultSarifTemplateURL = "https://raw.githubusercontent.com/aquasecurity/trivy/master/contrib/sarif.tpl"
)
type Results []Result
type Result struct {
@@ -65,6 +71,12 @@ func WriteResults(format string, output io.Writer, results Results, outputTempla
return xerrors.Errorf("error parsing template: %w", err)
}
writer = &TemplateWriter{Output: output, Template: tmpl}
case "sarif":
template, err := getSarifTemplate(DefaultSarifTemplateURL)
if err != nil {
return err
}
return WriteResults("template", output, results, template, light)
default:
return xerrors.Errorf("unknown format: %v", format)
}
@@ -75,6 +87,15 @@ func WriteResults(format string, output io.Writer, results Results, outputTempla
return nil
}
func getSarifTemplate(url string) (string, error) {
r, err := http.Get(url)
if err != nil {
return "", errors.New(fmt.Sprintf("error fetching template: %s", err))
}
b, _ := ioutil.ReadAll(r.Body)
return string(b), nil
}
type Writer interface {
Write(Results) error
}

View File

@@ -3,6 +3,9 @@ package report_test
import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
@@ -193,7 +196,6 @@ func TestReportWriter_JSON(t *testing.T) {
assert.Equal(t, tc.expectedJSON, writtenResults, tc.name)
})
}
}
func TestReportWriter_Template(t *testing.T) {
@@ -313,3 +315,263 @@ func TestReportWriter_Template(t *testing.T) {
})
}
}
func TestReportWriter_Sarif(t *testing.T) {
testCases := []struct {
name string
url string
expectedOutput string
expectedError string
}{
{
name: "happy path",
expectedOutput: `{
"$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.4.json",
"version": "2.1.0",
"runs": [{
"tool": {
"driver": {
"name": "Trivy",
"fullName": "Trivy Vulnerability Scanner",
"rules": [{
"id": "[MEDIUM] CVE-2019-0000",
"name": "dockerfile_scan",
"shortDescription": {
"text": "CVE-2019-0000 Package: foo"
},
"fullDescription": {
"text": "."
},
"help": {
"text": "Vulnerability CVE-2019-0000\nSeverity: MEDIUM\nPackage: foo\nInstalled Version: 1.2.3\nFixed Version: 1.2.4\nLink: [CVE-2019-0000](https://aquasecurity.github.io/avd/nvd/cve-2019-0000)",
"markdown": "**Vulnerability CVE-2019-0000**\n| Severity | Package | Installed Version | Fixed Version | Link |\n| --- | --- | --- | --- | --- |\n|MEDIUM|foo|1.2.3|1.2.4|[CVE-2019-0000](https://aquasecurity.github.io/avd/nvd/cve-2019-0000)|\n"
},
"properties": {
"tags": [
"vulnerability",
"MEDIUM",
"foo"
],
"precision": "very-high"
}
},
{
"id": "[HIGH] CVE-2019-0001",
"name": "dockerfile_scan",
"shortDescription": {
"text": "CVE-2019-0001 Package: bar"
},
"fullDescription": {
"text": "."
},
"help": {
"text": "Vulnerability CVE-2019-0001\nSeverity: HIGH\nPackage: bar\nInstalled Version: 2.3.4\nFixed Version: 2.3.5\nLink: [CVE-2019-0001](https://aquasecurity.github.io/avd/nvd/cve-2019-0001)",
"markdown": "**Vulnerability CVE-2019-0001**\n| Severity | Package | Installed Version | Fixed Version | Link |\n| --- | --- | --- | --- | --- |\n|HIGH|bar|2.3.4|2.3.5|[CVE-2019-0001](https://aquasecurity.github.io/avd/nvd/cve-2019-0001)|\n"
},
"properties": {
"tags": [
"vulnerability",
"HIGH",
"bar"
],
"precision": "very-high"
}
}
]
}
},
"results": [{
"ruleId": "[MEDIUM] CVE-2019-0000",
"ruleIndex": 0,
"level": "error",
"message": {
"text": "without period."
},
"locations": [{
"physicalLocation": {
"artifactLocation": {
"uri": "Dockerfile"
},
"region": {
"startLine": 1,
"startColumn": 1,
"endColumn": 1
}
}
}]
},
{
"ruleId": "[HIGH] CVE-2019-0001",
"ruleIndex": 1,
"level": "error",
"message": {
"text": "with period."
},
"locations": [{
"physicalLocation": {
"artifactLocation": {
"uri": "Dockerfile"
},
"region": {
"startLine": 1,
"startColumn": 1,
"endColumn": 1
}
}
}]
}
],
"columnKind": "utf16CodeUnits"
}]
}`,
},
{
name: "sad path, bad url",
url: "http://foo/bar/baz",
expectedError: "no such host",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
oldDefaultSarifTemplateURL := report.DefaultSarifTemplateURL
if tc.url == "" {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, `{
"$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.4.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"name": "Trivy",
"fullName": "Trivy Vulnerability Scanner",
"rules": [
{{- $t_first := true }}
{{- range . }}
{{- range .Vulnerabilities -}}
{{- if $t_first -}}
{{- $t_first = false -}}
{{ else -}}
,
{{- end }}
{
"id": "[{{ .Vulnerability.Severity }}] {{ .VulnerabilityID }}",
"name": "dockerfile_scan",
"shortDescription": {
"text": "{{ .VulnerabilityID }} Package: {{ .PkgName }}"
},
"fullDescription": {
"text": "{{ endWithPeriod .Title }}"
},
"help": {
"text": "Vulnerability {{ .VulnerabilityID }}\nSeverity: {{ .Vulnerability.Severity }}\nPackage: {{ .PkgName }}\nInstalled Version: {{ .InstalledVersion }}\nFixed Version: {{ .FixedVersion }}\nLink: [{{ .VulnerabilityID }}](https://aquasecurity.github.io/avd/nvd/{{ .VulnerabilityID | toLower}})",
"markdown": "**Vulnerability {{ .VulnerabilityID }}**\n| Severity | Package | Installed Version | Fixed Version | Link |\n| --- | --- | --- | --- | --- |\n|{{ .Vulnerability.Severity }}|{{ .PkgName }}|{{ .InstalledVersion }}|{{ .FixedVersion }}|[{{ .VulnerabilityID }}](https://aquasecurity.github.io/avd/nvd/{{ .VulnerabilityID | toLower }})|\n"
},
"properties": {
"tags": [
"vulnerability",
"{{ .Vulnerability.Severity }}",
"{{ .PkgName }}"
],
"precision": "very-high"
}
}
{{- end -}}
{{- end -}}
]
}
},
"results": [
{{- $t_first := true }}
{{- range . }}
{{- range $index, $vulnerability := .Vulnerabilities -}}
{{- if $t_first -}}
{{- $t_first = false -}}
{{ else -}}
,
{{- end }}
{
"ruleId": "[{{ $vulnerability.Vulnerability.Severity }}] {{ $vulnerability.VulnerabilityID }}",
"ruleIndex": {{ $index }},
"level": "error",
"message": {
"text": {{ endWithPeriod $vulnerability.Description | printf "%q" }}
},
"locations": [{
"physicalLocation": {
"artifactLocation": {
"uri": "Dockerfile"
},
"region": {
"startLine": 1,
"startColumn": 1,
"endColumn": 1
}
}
}]
}
{{- end -}}
{{- end -}}
],
"columnKind": "utf16CodeUnits"
}
]
}`)
}))
report.DefaultSarifTemplateURL = ts.URL
defer func() {
ts.Close()
report.DefaultSarifTemplateURL = oldDefaultSarifTemplateURL
}()
} else {
oldDefaultSarifTemplateURL := report.DefaultSarifTemplateURL
report.DefaultSarifTemplateURL = tc.url
defer func() {
report.DefaultSarifTemplateURL = oldDefaultSarifTemplateURL
}()
}
tmplWritten := bytes.Buffer{}
inputResults := report.Results{
{
Target: "sariftesttarget",
Type: "test",
Vulnerabilities: []types.DetectedVulnerability{
{
VulnerabilityID: "CVE-2019-0000",
PkgName: "foo",
Vulnerability: dbTypes.Vulnerability{
Severity: "MEDIUM",
Description: "without period",
},
InstalledVersion: "1.2.3",
FixedVersion: "1.2.4",
},
{
VulnerabilityID: "CVE-2019-0001",
PkgName: "bar",
Vulnerability: dbTypes.Vulnerability{
Severity: "HIGH",
Description: "with period.",
},
InstalledVersion: "2.3.4",
FixedVersion: "2.3.5",
},
},
},
}
err := report.WriteResults("sarif", &tmplWritten, inputResults, "", false)
switch {
case tc.expectedError != "":
assert.Contains(t, err.Error(), tc.expectedError, tc.name)
assert.Empty(t, tmplWritten.String(), tc.name)
default:
assert.NoError(t, err, tc.name)
assert.JSONEq(t, tc.expectedOutput, tmplWritten.String(), tc.name)
}
})
}
}