mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-21 23:00:42 -08:00
BREAKING: migrate to a new JSON schema (#782)
* feat: introduce a new JSON schema * test: update * chore(mod): update fanal * refactor: add a comment * test(report): fix * refactor(writer): add omitempty * refactor: replace url * test(scanner): fix
This commit is contained in:
2
go.mod
2
go.mod
@@ -3,7 +3,7 @@ module github.com/aquasecurity/trivy
|
|||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Masterminds/goutils v1.1.0 // indirect
|
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||||
github.com/Masterminds/sprig v2.22.0+incompatible
|
github.com/Masterminds/sprig v2.22.0+incompatible
|
||||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46
|
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46
|
||||||
github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986
|
github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -103,8 +103,8 @@ github.com/GoogleCloudPlatform/docker-credential-gcr v1.5.0/go.mod h1:BB1eHdMLYE
|
|||||||
github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14=
|
github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14=
|
||||||
github.com/KeisukeYamashita/go-vcl v0.4.0/go.mod h1:af2qGlXbsHDQN5abN7hyGNKtGhcFSaDdbLl4sfud+AU=
|
github.com/KeisukeYamashita/go-vcl v0.4.0/go.mod h1:af2qGlXbsHDQN5abN7hyGNKtGhcFSaDdbLl4sfud+AU=
|
||||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||||
github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
|
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||||
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||||
github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||||
"github.com/aquasecurity/trivy/pkg/commands/operation"
|
"github.com/aquasecurity/trivy/pkg/commands/operation"
|
||||||
"github.com/aquasecurity/trivy/pkg/log"
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
"github.com/aquasecurity/trivy/pkg/report"
|
pkgReport "github.com/aquasecurity/trivy/pkg/report"
|
||||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
"github.com/aquasecurity/trivy/pkg/scanner"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
"github.com/aquasecurity/trivy/pkg/utils"
|
"github.com/aquasecurity/trivy/pkg/utils"
|
||||||
@@ -62,21 +62,21 @@ func runWithTimeout(ctx context.Context, opt Option, initializeScanner Initializ
|
|||||||
defer db.Close()
|
defer db.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
results, err := scan(ctx, opt, initializeScanner, cacheClient)
|
report, err := scan(ctx, opt, initializeScanner, cacheClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("scan error: %w", err)
|
return xerrors.Errorf("scan error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
results, err = filter(ctx, opt, results)
|
report, err = filter(ctx, opt, report)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("filter error: %w", err)
|
return xerrors.Errorf("filter error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = report.WriteResults(opt.Format, opt.Output, opt.Severities, results, opt.Template, opt.Light); err != nil {
|
if err = pkgReport.Write(opt.Format, opt.Output, opt.Severities, report, opt.Template, opt.Light); err != nil {
|
||||||
return xerrors.Errorf("unable to write results: %w", err)
|
return xerrors.Errorf("unable to write results: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
exit(opt, results)
|
exit(opt, report.Results)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -124,7 +124,7 @@ func initDB(c Option) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func scan(ctx context.Context, opt Option, initializeScanner InitializeScanner, cacheClient cache.Cache) (
|
func scan(ctx context.Context, opt Option, initializeScanner InitializeScanner, cacheClient cache.Cache) (
|
||||||
report.Results, error) {
|
pkgReport.Report, error) {
|
||||||
target := opt.Target
|
target := opt.Target
|
||||||
if opt.Input != "" {
|
if opt.Input != "" {
|
||||||
target = opt.Input
|
target = opt.Input
|
||||||
@@ -154,32 +154,33 @@ func scan(ctx context.Context, opt Option, initializeScanner InitializeScanner,
|
|||||||
s, cleanup, err := initializeScanner(ctx, target, cacheClient, cacheClient, opt.Timeout,
|
s, cleanup, err := initializeScanner(ctx, target, cacheClient, cacheClient, opt.Timeout,
|
||||||
disabledAnalyzers, configScannerOptions)
|
disabledAnalyzers, configScannerOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("unable to initialize a scanner: %w", err)
|
return pkgReport.Report{}, xerrors.Errorf("unable to initialize a scanner: %w", err)
|
||||||
}
|
}
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
results, err := s.ScanArtifact(ctx, scanOptions)
|
report, err := s.ScanArtifact(ctx, scanOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("image scan failed: %w", err)
|
return pkgReport.Report{}, xerrors.Errorf("image scan failed: %w", err)
|
||||||
}
|
}
|
||||||
return results, nil
|
return report, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func filter(ctx context.Context, opt Option, results report.Results) (report.Results, error) {
|
func filter(ctx context.Context, opt Option, report pkgReport.Report) (pkgReport.Report, error) {
|
||||||
resultClient := initializeResultClient()
|
resultClient := initializeResultClient()
|
||||||
|
results := report.Results
|
||||||
for i := range results {
|
for i := range results {
|
||||||
resultClient.FillInfo(results[i].Vulnerabilities, results[i].Type)
|
resultClient.FillInfo(results[i].Vulnerabilities, results[i].Type)
|
||||||
vulns, err := resultClient.Filter(ctx, results[i].Vulnerabilities,
|
vulns, err := resultClient.Filter(ctx, results[i].Vulnerabilities,
|
||||||
opt.Severities, opt.IgnoreUnfixed, opt.IgnoreFile, opt.IgnorePolicy)
|
opt.Severities, opt.IgnoreUnfixed, opt.IgnoreFile, opt.IgnorePolicy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("unable to filter vulnerabilities: %w", err)
|
return pkgReport.Report{}, xerrors.Errorf("unable to filter vulnerabilities: %w", err)
|
||||||
}
|
}
|
||||||
results[i].Vulnerabilities = vulns
|
results[i].Vulnerabilities = vulns
|
||||||
}
|
}
|
||||||
return results, nil
|
return report, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func exit(c Option, results report.Results) {
|
func exit(c Option, results pkgReport.Results) {
|
||||||
if c.ExitCode != 0 && results.Failed() {
|
if c.ExitCode != 0 && results.Failed() {
|
||||||
os.Exit(c.ExitCode)
|
os.Exit(c.ExitCode)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/aquasecurity/fanal/analyzer/config"
|
"github.com/aquasecurity/fanal/analyzer/config"
|
||||||
"github.com/aquasecurity/trivy/pkg/cache"
|
"github.com/aquasecurity/trivy/pkg/cache"
|
||||||
"github.com/aquasecurity/trivy/pkg/log"
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
"github.com/aquasecurity/trivy/pkg/report"
|
pkgReport "github.com/aquasecurity/trivy/pkg/report"
|
||||||
"github.com/aquasecurity/trivy/pkg/rpc/client"
|
"github.com/aquasecurity/trivy/pkg/rpc/client"
|
||||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
"github.com/aquasecurity/trivy/pkg/scanner"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
@@ -59,12 +59,13 @@ func runWithTimeout(ctx context.Context, opt Option) error {
|
|||||||
}
|
}
|
||||||
log.Logger.Debugf("Vulnerability type: %s", scanOptions.VulnType)
|
log.Logger.Debugf("Vulnerability type: %s", scanOptions.VulnType)
|
||||||
|
|
||||||
results, err := s.ScanArtifact(ctx, scanOptions)
|
report, err := s.ScanArtifact(ctx, scanOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("error in image scan: %w", err)
|
return xerrors.Errorf("error in image scan: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resultClient := initializeResultClient()
|
resultClient := initializeResultClient()
|
||||||
|
results := report.Results
|
||||||
for i := range results {
|
for i := range results {
|
||||||
vulns, err := resultClient.Filter(ctx, results[i].Vulnerabilities,
|
vulns, err := resultClient.Filter(ctx, results[i].Vulnerabilities,
|
||||||
opt.Severities, opt.IgnoreUnfixed, opt.IgnoreFile, opt.IgnorePolicy)
|
opt.Severities, opt.IgnoreUnfixed, opt.IgnoreFile, opt.IgnorePolicy)
|
||||||
@@ -74,7 +75,7 @@ func runWithTimeout(ctx context.Context, opt Option) error {
|
|||||||
results[i].Vulnerabilities = vulns
|
results[i].Vulnerabilities = vulns
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = report.WriteResults(opt.Format, opt.Output, opt.Severities, results, opt.Template, false); err != nil {
|
if err = pkgReport.Write(opt.Format, opt.Output, opt.Severities, report, opt.Template, false); err != nil {
|
||||||
return xerrors.Errorf("unable to write results: %w", err)
|
return xerrors.Errorf("unable to write results: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +136,7 @@ func initializeScanner(ctx context.Context, opt Option) (scanner.Scanner, func()
|
|||||||
return s, cleanup, nil
|
return s, cleanup, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func exit(c Option, results report.Results) {
|
func exit(c Option, results pkgReport.Results) {
|
||||||
if c.ExitCode != 0 {
|
if c.ExitCode != 0 {
|
||||||
for _, result := range results {
|
for _, result := range results {
|
||||||
if len(result.Vulnerabilities) > 0 {
|
if len(result.Vulnerabilities) > 0 {
|
||||||
|
|||||||
@@ -4,8 +4,11 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// JSONWriter implements result Writer
|
// JSONWriter implements result Writer
|
||||||
@@ -14,8 +17,16 @@ type JSONWriter struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write writes the results in JSON format
|
// Write writes the results in JSON format
|
||||||
func (jw JSONWriter) Write(results Results) error {
|
func (jw JSONWriter) Write(report Report) error {
|
||||||
output, err := json.MarshalIndent(results, "", " ")
|
var v interface{} = report
|
||||||
|
if os.Getenv("TRIVY_NEW_JSON_SCHEMA") == "" {
|
||||||
|
// After migrating to the new JSON schema, TRIVY_NEW_JSON_SCHEMA will be removed.
|
||||||
|
log.Logger.Warnf("DEPRECATED: the current JSON schema is deprecated, check %s for more information.",
|
||||||
|
"https://github.com/aquasecurity/trivy/discussions/1050")
|
||||||
|
v = report.Results
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := json.MarshalIndent(v, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("failed to marshal json: %w", err)
|
return xerrors.Errorf("failed to marshal json: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,14 +62,16 @@ func TestReportWriter_JSON(t *testing.T) {
|
|||||||
jsonWritten := bytes.Buffer{}
|
jsonWritten := bytes.Buffer{}
|
||||||
jw.Output = &jsonWritten
|
jw.Output = &jsonWritten
|
||||||
|
|
||||||
inputResults := report.Results{
|
inputResults := report.Report{
|
||||||
|
Results: report.Results{
|
||||||
{
|
{
|
||||||
Target: "foojson",
|
Target: "foojson",
|
||||||
Vulnerabilities: tc.detectedVulns,
|
Vulnerabilities: tc.detectedVulns,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := report.WriteResults("json", &jsonWritten, nil, inputResults, "", false)
|
err := report.Write("json", &jsonWritten, nil, inputResults, "", false)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
writtenResults := report.Results{}
|
writtenResults := report.Results{}
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ type TableWriter struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write writes the result on standard output
|
// Write writes the result on standard output
|
||||||
func (tw TableWriter) Write(results Results) error {
|
func (tw TableWriter) Write(report Report) error {
|
||||||
for _, result := range results {
|
for _, result := range report.Results {
|
||||||
// Skip zero vulnerabilities on Java archives (JAR/WAR/EAR)
|
// Skip zero vulnerabilities on Java archives (JAR/WAR/EAR)
|
||||||
if result.Type == ftypes.Jar && len(result.Vulnerabilities) == 0 {
|
if result.Type == ftypes.Jar && len(result.Vulnerabilities) == 0 {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ func TestReportWriter_Table(t *testing.T) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
tableWritten := bytes.Buffer{}
|
tableWritten := bytes.Buffer{}
|
||||||
assert.NoError(t, report.WriteResults("table", &tableWritten, nil, tc.results, "", tc.light), tc.name)
|
assert.NoError(t, report.Write("table", &tableWritten, nil, report.Report{Results: tc.results}, "", tc.light), tc.name)
|
||||||
assert.Equal(t, tc.expectedOutput, tableWritten.String(), tc.name)
|
assert.Equal(t, tc.expectedOutput, tableWritten.String(), tc.name)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ import (
|
|||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability"
|
|
||||||
|
|
||||||
"github.com/Masterminds/sprig"
|
"github.com/Masterminds/sprig"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability"
|
||||||
)
|
)
|
||||||
|
|
||||||
// regex to extract file path in case string includes (distro:version)
|
// regex to extract file path in case string includes (distro:version)
|
||||||
@@ -83,8 +83,8 @@ func NewTemplateWriter(output io.Writer, outputTemplate string) (*TemplateWriter
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write writes result
|
// Write writes result
|
||||||
func (tw TemplateWriter) Write(results Results) error {
|
func (tw TemplateWriter) Write(report Report) error {
|
||||||
err := tw.Template.Execute(tw.Output, results)
|
err := tw.Template.Execute(tw.Output, report.Results)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("failed to write with template: %w", err)
|
return xerrors.Errorf("failed to write with template: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,15 +166,17 @@ func TestReportWriter_Template(t *testing.T) {
|
|||||||
}
|
}
|
||||||
os.Setenv("AWS_ACCOUNT_ID", "123456789012")
|
os.Setenv("AWS_ACCOUNT_ID", "123456789012")
|
||||||
tmplWritten := bytes.Buffer{}
|
tmplWritten := bytes.Buffer{}
|
||||||
inputResults := report.Results{
|
inputReport := report.Report{
|
||||||
|
Results: report.Results{
|
||||||
{
|
{
|
||||||
Target: "foojunit",
|
Target: "foojunit",
|
||||||
Type: "test",
|
Type: "test",
|
||||||
Vulnerabilities: tc.detectedVulns,
|
Vulnerabilities: tc.detectedVulns,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.NoError(t, report.WriteResults("template", &tmplWritten, nil, inputResults, tc.template, false))
|
assert.NoError(t, report.Write("template", &tmplWritten, nil, inputReport, tc.template, false))
|
||||||
assert.Equal(t, tc.expected, tmplWritten.String())
|
assert.Equal(t, tc.expected, tmplWritten.String())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -197,14 +199,16 @@ func TestReportWriter_Template_SARIF(t *testing.T) {
|
|||||||
template, err := ioutil.ReadFile(templateFile)
|
template, err := ioutil.ReadFile(templateFile)
|
||||||
require.NoError(t, err, tc.name)
|
require.NoError(t, err, tc.name)
|
||||||
|
|
||||||
inputResults := report.Results{
|
inputReport := report.Report{
|
||||||
report.Result{
|
Results: report.Results{
|
||||||
|
{
|
||||||
Target: tc.target,
|
Target: tc.target,
|
||||||
Type: "footype",
|
Type: "footype",
|
||||||
Vulnerabilities: tc.detectedVulns,
|
Vulnerabilities: tc.detectedVulns,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
assert.NoError(t, report.WriteResults("template", &got, nil, inputResults, string(template), false), tc.name)
|
assert.NoError(t, report.Write("template", &got, nil, inputReport, string(template), false), tc.name)
|
||||||
assert.JSONEq(t, tc.want, got.String(), tc.name)
|
assert.JSONEq(t, tc.want, got.String(), tc.name)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
27
pkg/report/testdata/new.json.golden
vendored
Normal file
27
pkg/report/testdata/new.json.golden
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"ImageID": "sha256:5c534be56eca62e756ef2ef51523feda0f19cd7c15bb0c015e3d6e3ae090bf6e",
|
||||||
|
"RepoTags": [
|
||||||
|
"test:latest"
|
||||||
|
],
|
||||||
|
"RepoDigests": [
|
||||||
|
"test@sha256:0bd0e9e03a022c3b0226667621da84fc9bf562a9056130424b5bfbd8bcb0397f"
|
||||||
|
],
|
||||||
|
"Results": [
|
||||||
|
{
|
||||||
|
"Target": "foojson",
|
||||||
|
"Vulnerabilities": [
|
||||||
|
{
|
||||||
|
"VulnerabilityID": "CVE-2020-0001",
|
||||||
|
"PkgName": "foo",
|
||||||
|
"InstalledVersion": "1.2.3",
|
||||||
|
"FixedVersion": "3.4.5",
|
||||||
|
"Layer": {},
|
||||||
|
"PrimaryURL": "https://avd.aquasec.com/nvd/cve-2020-0001",
|
||||||
|
"Title": "foobar",
|
||||||
|
"Description": "baz",
|
||||||
|
"Severity": "HIGH"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
18
pkg/report/testdata/old.json.golden
vendored
Normal file
18
pkg/report/testdata/old.json.golden
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"Target": "foojson",
|
||||||
|
"Vulnerabilities": [
|
||||||
|
{
|
||||||
|
"VulnerabilityID": "CVE-2020-0001",
|
||||||
|
"PkgName": "foo",
|
||||||
|
"InstalledVersion": "1.2.3",
|
||||||
|
"FixedVersion": "3.4.5",
|
||||||
|
"Layer": {},
|
||||||
|
"PrimaryURL": "https://avd.aquasec.com/nvd/cve-2020-0001",
|
||||||
|
"Title": "foobar",
|
||||||
|
"Description": "baz",
|
||||||
|
"Severity": "HIGH"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -14,10 +14,18 @@ import (
|
|||||||
// Now returns the current time
|
// Now returns the current time
|
||||||
var Now = time.Now
|
var Now = time.Now
|
||||||
|
|
||||||
|
// Report represents a scan result
|
||||||
|
type Report struct {
|
||||||
|
ArtifactID string `json:",omitempty"`
|
||||||
|
RepoTags []string `json:",omitempty"`
|
||||||
|
RepoDigests []string `json:",omitempty"`
|
||||||
|
Results Results `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// Results to hold list of Result
|
// Results to hold list of Result
|
||||||
type Results []Result
|
type Results []Result
|
||||||
|
|
||||||
// Result to hold image scan results
|
// Result holds a target and detected vulnerabilities
|
||||||
type Result struct {
|
type Result struct {
|
||||||
Target string `json:"Target"`
|
Target string `json:"Target"`
|
||||||
Type string `json:"Type,omitempty"`
|
Type string `json:"Type,omitempty"`
|
||||||
@@ -35,8 +43,9 @@ func (results Results) Failed() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteResults writes the result to output, format as passed in argument
|
// Write writes the result to output, format as passed in argument
|
||||||
func WriteResults(format string, output io.Writer, severities []dbTypes.Severity, results Results, outputTemplate string, light bool) error {
|
func Write(format string, output io.Writer, severities []dbTypes.Severity, report Report,
|
||||||
|
outputTemplate string, light bool) error {
|
||||||
var writer Writer
|
var writer Writer
|
||||||
switch format {
|
switch format {
|
||||||
case "table":
|
case "table":
|
||||||
@@ -52,7 +61,7 @@ func WriteResults(format string, output io.Writer, severities []dbTypes.Severity
|
|||||||
return xerrors.Errorf("unknown format: %v", format)
|
return xerrors.Errorf("unknown format: %v", format)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := writer.Write(results); err != nil {
|
if err := writer.Write(report); err != nil {
|
||||||
return xerrors.Errorf("failed to write results: %w", err)
|
return xerrors.Errorf("failed to write results: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -60,5 +69,5 @@ func WriteResults(format string, output io.Writer, severities []dbTypes.Severity
|
|||||||
|
|
||||||
// Writer defines the result write operation
|
// Writer defines the result write operation
|
||||||
type Writer interface {
|
type Writer interface {
|
||||||
Write(Results) error
|
Write(Report) error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ package report_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/report"
|
"github.com/aquasecurity/trivy/pkg/report"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestResults_Failed(t *testing.T) {
|
func TestResults_Failed(t *testing.T) {
|
||||||
|
|||||||
@@ -92,20 +92,25 @@ func NewScanner(driver Driver, ar artifact.Artifact) Scanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ScanArtifact scans the artifacts and returns results
|
// ScanArtifact scans the artifacts and returns results
|
||||||
func (s Scanner) ScanArtifact(ctx context.Context, options types.ScanOptions) (report.Results, error) {
|
func (s Scanner) ScanArtifact(ctx context.Context, options types.ScanOptions) (report.Report, error) {
|
||||||
artifactInfo, err := s.artifact.Inspect(ctx)
|
artifactInfo, err := s.artifact.Inspect(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("failed analysis: %w", err)
|
return report.Report{}, xerrors.Errorf("failed analysis: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
results, osFound, eosl, err := s.driver.Scan(artifactInfo.Name, artifactInfo.ID, artifactInfo.BlobIDs, options)
|
results, osFound, eosl, err := s.driver.Scan(artifactInfo.Name, artifactInfo.ID, artifactInfo.BlobIDs, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("scan failed: %w", err)
|
return report.Report{}, xerrors.Errorf("scan failed: %w", err)
|
||||||
}
|
}
|
||||||
if eosl {
|
if eosl {
|
||||||
log.Logger.Warnf("This OS version is no longer supported by the distribution: %s %s", osFound.Family, osFound.Name)
|
log.Logger.Warnf("This OS version is no longer supported by the distribution: %s %s", osFound.Family, osFound.Name)
|
||||||
log.Logger.Warnf("The vulnerability detection may be insufficient because security updates are not provided")
|
log.Logger.Warnf("The vulnerability detection may be insufficient because security updates are not provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
return results, nil
|
return report.Report{
|
||||||
|
ArtifactID: artifactInfo.ID,
|
||||||
|
RepoTags: artifactInfo.RepoTags,
|
||||||
|
RepoDigests: artifactInfo.RepoDigests,
|
||||||
|
Results: results,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func TestScanner_ScanArtifact(t *testing.T) {
|
|||||||
args args
|
args args
|
||||||
inspectExpectation artifact.ArtifactInspectExpectation
|
inspectExpectation artifact.ArtifactInspectExpectation
|
||||||
scanExpectation DriverScanExpectation
|
scanExpectation DriverScanExpectation
|
||||||
wantResults report.Results
|
want report.Report
|
||||||
wantErr string
|
wantErr string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@@ -40,6 +40,8 @@ func TestScanner_ScanArtifact(t *testing.T) {
|
|||||||
Name: "alpine:3.11",
|
Name: "alpine:3.11",
|
||||||
ID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a",
|
ID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a",
|
||||||
BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"},
|
BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"},
|
||||||
|
RepoTags: []string{"alpine:3.11"},
|
||||||
|
RepoDigests: []string{"alpine@sha256:0bd0e9e03a022c3b0226667621da84fc9bf562a9056130424b5bfbd8bcb0397f"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -87,7 +89,11 @@ func TestScanner_ScanArtifact(t *testing.T) {
|
|||||||
Eols: true,
|
Eols: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantResults: report.Results{
|
want: report.Report{
|
||||||
|
ArtifactID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a",
|
||||||
|
RepoTags: []string{"alpine:3.11"},
|
||||||
|
RepoDigests: []string{"alpine@sha256:0bd0e9e03a022c3b0226667621da84fc9bf562a9056130424b5bfbd8bcb0397f"},
|
||||||
|
Results: report.Results{
|
||||||
{
|
{
|
||||||
Target: "alpine:3.11",
|
Target: "alpine:3.11",
|
||||||
Vulnerabilities: []types.DetectedVulnerability{
|
Vulnerabilities: []types.DetectedVulnerability{
|
||||||
@@ -117,6 +123,7 @@ func TestScanner_ScanArtifact(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "sad path: AnalyzerAnalyze returns an error",
|
name: "sad path: AnalyzerAnalyze returns an error",
|
||||||
args: args{
|
args: args{
|
||||||
@@ -172,7 +179,7 @@ func TestScanner_ScanArtifact(t *testing.T) {
|
|||||||
mockArtifact.ApplyInspectExpectation(tt.inspectExpectation)
|
mockArtifact.ApplyInspectExpectation(tt.inspectExpectation)
|
||||||
|
|
||||||
s := NewScanner(d, mockArtifact)
|
s := NewScanner(d, mockArtifact)
|
||||||
gotResults, err := s.ScanArtifact(context.Background(), tt.args.options)
|
got, err := s.ScanArtifact(context.Background(), tt.args.options)
|
||||||
if tt.wantErr != "" {
|
if tt.wantErr != "" {
|
||||||
require.NotNil(t, err, tt.name)
|
require.NotNil(t, err, tt.name)
|
||||||
require.Contains(t, err.Error(), tt.wantErr, tt.name)
|
require.Contains(t, err.Error(), tt.wantErr, tt.name)
|
||||||
@@ -181,7 +188,7 @@ func TestScanner_ScanArtifact(t *testing.T) {
|
|||||||
require.NoError(t, err, tt.name)
|
require.NoError(t, err, tt.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, tt.wantResults, gotResults, tt.name)
|
assert.Equal(t, tt.want, got, tt.name)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user