Files
trivy/pkg/report/writer.go
Simarpreet Singh 4d721e1410 SARIF: Tweak format for GitHub UI (#571)
* sarif: Tweak format for GitHub UI

Signed-off-by: Simarpreet Singh <simar@linux.com>

* 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>

* Revert "sarif: Make sarif easier to use with a default template"

This reverts commit 5b5d1c8f7d.

* .dockerignore: Add un-needed large directories

Signed-off-by: Simarpreet Singh <simar@linux.com>

* Dockerfile: Add sarif template.

This will let users run and save the output through the docker image

Example:
```
docker run --rm -it -v $(pwd):/tmp aquasec/trivy:latest image -f template --template "@contrib/sarif.tpl" --output="/tmp/sarif.test" alpine:3.10.2
```

Signed-off-by: Simarpreet Singh <simar@linux.com>
2020-07-28 11:22:03 -07:00

175 lines
4.2 KiB
Go

package report
import (
"bytes"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"text/template"
"golang.org/x/xerrors"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/olekukonko/tablewriter"
)
type Results []Result
type Result struct {
Target string `json:"Target"`
Type string `json:"Type,omitempty"`
Vulnerabilities []types.DetectedVulnerability `json:"Vulnerabilities"`
}
func WriteResults(format string, output io.Writer, results Results, outputTemplate string, light bool) error {
var writer Writer
switch format {
case "table":
writer = &TableWriter{Output: output, Light: light}
case "json":
writer = &JsonWriter{Output: output}
case "template":
if strings.HasPrefix(outputTemplate, "@") {
buf, err := ioutil.ReadFile(strings.TrimPrefix(outputTemplate, "@"))
if err != nil {
return xerrors.Errorf("Error retrieving template from path: %w", err)
}
outputTemplate = string(buf)
}
tmpl, err := template.New("output template").Funcs(template.FuncMap{
"escapeXML": func(input string) string {
escaped := &bytes.Buffer{}
if err := xml.EscapeText(escaped, []byte(input)); err != nil {
fmt.Printf("error while escapeString to XML: %v", err.Error())
return input
}
return escaped.String()
},
"endWithPeriod": func(input string) string {
if !strings.HasSuffix(input, ".") {
input += "."
}
return input
},
"toLower": func(input string) string {
return strings.ToLower(input)
},
}).Parse(outputTemplate)
if err != nil {
return xerrors.Errorf("error parsing template: %w", err)
}
writer = &TemplateWriter{Output: output, Template: tmpl}
default:
return xerrors.Errorf("unknown format: %v", format)
}
if err := writer.Write(results); err != nil {
return xerrors.Errorf("failed to write results: %w", err)
}
return nil
}
type Writer interface {
Write(Results) error
}
type TableWriter struct {
Output io.Writer
Light bool
}
func (tw TableWriter) Write(results Results) error {
for _, result := range results {
tw.write(result)
}
return nil
}
func (tw TableWriter) write(result Result) {
table := tablewriter.NewWriter(tw.Output)
header := []string{"Library", "Vulnerability ID", "Severity", "Installed Version", "Fixed Version"}
if !tw.Light {
header = append(header, "Title")
}
table.SetHeader(header)
severityCount := map[string]int{}
for _, v := range result.Vulnerabilities {
severityCount[v.Severity]++
title := v.Title
if title == "" {
title = v.Description
}
splittedTitle := strings.Split(title, " ")
if len(splittedTitle) >= 12 {
title = strings.Join(splittedTitle[:12], " ") + "..."
}
var row []string
if tw.Output == os.Stdout {
row = []string{v.PkgName, v.VulnerabilityID, dbTypes.ColorizeSeverity(v.Severity),
v.InstalledVersion, v.FixedVersion}
} else {
row = []string{v.PkgName, v.VulnerabilityID, v.Severity, v.InstalledVersion, v.FixedVersion}
}
if !tw.Light {
row = append(row, title)
}
table.Append(row)
}
var results []string
for _, severity := range dbTypes.SeverityNames {
r := fmt.Sprintf("%s: %d", severity, severityCount[severity])
results = append(results, r)
}
fmt.Printf("\n%s\n", result.Target)
fmt.Println(strings.Repeat("=", len(result.Target)))
fmt.Printf("Total: %d (%s)\n\n", len(result.Vulnerabilities), strings.Join(results, ", "))
if len(result.Vulnerabilities) == 0 {
return
}
table.SetAutoMergeCells(true)
table.SetRowLine(true)
table.Render()
return
}
type JsonWriter struct {
Output io.Writer
}
func (jw JsonWriter) Write(results Results) error {
output, err := json.MarshalIndent(results, "", " ")
if err != nil {
return xerrors.Errorf("failed to marshal json: %w", err)
}
if _, err = fmt.Fprint(jw.Output, string(output)); err != nil {
return xerrors.Errorf("failed to write json: %w", err)
}
return nil
}
type TemplateWriter struct {
Output io.Writer
Template *template.Template
}
func (tw TemplateWriter) Write(results Results) error {
err := tw.Template.Execute(tw.Output, results)
if err != nil {
return xerrors.Errorf("failed to write with template: %w", err)
}
return nil
}