mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-05 20:40:16 -08:00
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:
@@ -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
|
||||
|
||||
```
|
||||
|
||||
@@ -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"},
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user