feat(report): GitHub Dependency Snapshots support (#1522)

Co-authored-by: Shira Cohen <97398476+ShiraCohen33@users.noreply.github.com>
Co-authored-by: knqyf263 <knqyf263@gmail.com>
This commit is contained in:
AndreyLevchenko
2022-05-27 00:34:15 +06:00
committed by GitHub
parent b7ec642572
commit 4ab696eaa2
15 changed files with 565 additions and 28 deletions

View File

@@ -21,6 +21,7 @@ import (
testcontainers "github.com/testcontainers/testcontainers-go"
"github.com/urfave/cli/v2"
"github.com/aquasecurity/trivy/pkg/clock"
"github.com/aquasecurity/trivy/pkg/commands"
"github.com/aquasecurity/trivy/pkg/report"
)
@@ -256,7 +257,7 @@ func TestClientServer(t *testing.T) {
}
}
func TestClientServerWithTemplate(t *testing.T) {
func TestClientServerWithFormat(t *testing.T) {
tests := []struct {
name string
args csArgs
@@ -306,17 +307,35 @@ func TestClientServerWithTemplate(t *testing.T) {
},
golden: "testdata/alpine-310.html.golden",
},
{
name: "alpine 3.10 with github dependency snapshots format",
args: csArgs{
Format: "github",
Input: "testdata/fixtures/images/alpine-310.tar.gz",
},
golden: "testdata/alpine-310.gsbom.golden",
},
}
fakeTime := time.Date(2020, 8, 10, 7, 28, 17, 958601, time.UTC)
clock.SetFakeTime(t, fakeTime)
report.CustomTemplateFuncMap = map[string]interface{}{
"now": func() time.Time {
return time.Date(2020, 8, 10, 7, 28, 17, 958601, time.UTC)
return fakeTime
},
"date": func(format string, t time.Time) string {
return t.Format(format)
},
}
// For GitHub Dependency Snapshots
t.Setenv("GITHUB_REF", "/ref/feature-1")
t.Setenv("GITHUB_SHA", "39da54a1ff04120a31df8cbc94ce9ede251d21a3")
t.Setenv("GITHUB_JOB", "integration")
t.Setenv("GITHUB_RUN_ID", "1910764383")
t.Setenv("GITHUB_WORKFLOW", "workflow-name")
t.Cleanup(func() {
report.CustomTemplateFuncMap = map[string]interface{}{}
})

View File

@@ -0,0 +1,91 @@
{
"detector": {
"name": "trivy",
"version": "dev",
"url": "https://github.com/aquasecurity/trivy"
},
"ref": "/ref/feature-1",
"sha": "39da54a1ff04120a31df8cbc94ce9ede251d21a3",
"job": {
"correlator": "workflow-name_integration",
"id": "1910764383"
},
"scanned": "2020-08-10T07:28:17Z",
"manifests": {
"testdata/fixtures/images/alpine-310.tar.gz (alpine 3.10.2)": {
"name": "alpine",
"resolved": {
"alpine-baselayout": {
"package_url": "pkg:apk/alpine-baselayout@3.1.2-r0",
"relationship": "direct",
"scope": "runtime"
},
"alpine-keys": {
"package_url": "pkg:apk/alpine-keys@2.1-r2",
"relationship": "direct",
"scope": "runtime"
},
"apk-tools": {
"package_url": "pkg:apk/apk-tools@2.10.4-r2",
"relationship": "direct",
"scope": "runtime"
},
"busybox": {
"package_url": "pkg:apk/busybox@1.30.1-r2",
"relationship": "direct",
"scope": "runtime"
},
"ca-certificates-cacert": {
"package_url": "pkg:apk/ca-certificates-cacert@20190108-r0",
"relationship": "direct",
"scope": "runtime"
},
"libc-utils": {
"package_url": "pkg:apk/libc-utils@0.7.1-r0",
"relationship": "direct",
"scope": "runtime"
},
"libcrypto1.1": {
"package_url": "pkg:apk/libcrypto1.1@1.1.1c-r0",
"relationship": "direct",
"scope": "runtime"
},
"libssl1.1": {
"package_url": "pkg:apk/libssl1.1@1.1.1c-r0",
"relationship": "direct",
"scope": "runtime"
},
"libtls-standalone": {
"package_url": "pkg:apk/libtls-standalone@2.9.1-r0",
"relationship": "direct",
"scope": "runtime"
},
"musl": {
"package_url": "pkg:apk/musl@1.1.22-r3",
"relationship": "direct",
"scope": "runtime"
},
"musl-utils": {
"package_url": "pkg:apk/musl-utils@1.1.22-r3",
"relationship": "direct",
"scope": "runtime"
},
"scanelf": {
"package_url": "pkg:apk/scanelf@1.2.3-r0",
"relationship": "direct",
"scope": "runtime"
},
"ssl_client": {
"package_url": "pkg:apk/ssl_client@1.30.1-r2",
"relationship": "direct",
"scope": "runtime"
},
"zlib": {
"package_url": "pkg:apk/zlib@1.2.11-r1",
"relationship": "direct",
"scope": "runtime"
}
}
}
}
}

23
pkg/clock/clock.go Normal file
View File

@@ -0,0 +1,23 @@
package clock
import (
"testing"
"time"
"k8s.io/utils/clock"
clocktesting "k8s.io/utils/clock/testing"
)
var c clock.Clock = clock.RealClock{}
// SetFakeTime sets a fake time for testing.
func SetFakeTime(t *testing.T, fakeTime time.Time) {
c = clocktesting.NewFakeClock(fakeTime)
t.Cleanup(func() {
c = clock.RealClock{}
})
}
func Now() time.Time {
return c.Now()
}

View File

@@ -18,6 +18,7 @@ import (
"github.com/aquasecurity/trivy/pkg/commands/server"
"github.com/aquasecurity/trivy/pkg/k8s"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/report"
"github.com/aquasecurity/trivy/pkg/result"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/aquasecurity/trivy/pkg/utils"
@@ -41,8 +42,8 @@ var (
formatFlag = cli.StringFlag{
Name: "format",
Aliases: []string{"f"},
Value: "table",
Usage: "format (table, json, sarif, template)",
Value: report.FormatTable,
Usage: "format (table, json, sarif, template, cyclonedx, spdx, spdx-json, github)",
EnvVars: []string{"TRIVY_FORMAT"},
}
@@ -906,8 +907,8 @@ func NewSbomCommand() *cli.Command {
&cli.StringFlag{
Name: "sbom-format",
Aliases: []string{"format"},
Value: "cyclonedx",
Usage: "SBOM format (cyclonedx, spdx, spdx-json)",
Value: report.FormatCycloneDX,
Usage: "SBOM format (cyclonedx, spdx, spdx-json, github)",
EnvVars: []string{"TRIVY_SBOM_FORMAT"},
},
},

View File

@@ -14,6 +14,7 @@ import (
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/commands/option"
"github.com/aquasecurity/trivy/pkg/report"
"github.com/aquasecurity/trivy/pkg/types"
)
@@ -208,6 +209,23 @@ func TestOption_Init(t *testing.T) {
},
},
},
{
name: "json and list all packages",
args: []string{"--format", "json", "--list-all-pkgs", "gitlab/gitlab-ce:12.7.2-ce.0"},
want: Option{
ReportOption: option.ReportOption{
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
Output: os.Stdout,
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
SecurityChecks: []string{types.SecurityCheckVulnerability},
Format: "json",
ListAllPkgs: true,
},
ArtifactOption: option.ArtifactOption{
Target: "gitlab/gitlab-ce:12.7.2-ce.0",
},
},
},
{
name: "invalid option combination: --format template without --template",
args: []string{"--format", "template", "--severity", "MEDIUM", "gitlab/gitlab-ce:12.7.2-ce.0"},
@@ -227,6 +245,24 @@ func TestOption_Init(t *testing.T) {
},
},
},
{
name: "github enables list-all-pkgs",
args: []string{"--format", "github", "alpine:3.15"},
want: Option{
ReportOption: option.ReportOption{
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
Output: os.Stdout,
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
SecurityChecks: []string{types.SecurityCheckVulnerability},
Format: report.FormatGitHub,
ListAllPkgs: true,
},
ArtifactOption: option.ArtifactOption{
Target: "alpine:3.15",
},
},
},
{
name: "sad: skip and download db",
args: []string{"--skip-db-update", "--download-db-only", "alpine:3.10"},
@@ -253,6 +289,7 @@ func TestOption_Init(t *testing.T) {
set.Bool("reset", false, "")
set.Bool("skip-db-update", false, "")
set.Bool("download-db-only", false, "")
set.Bool("list-all-pkgs", false, "")
set.String("severity", "CRITICAL", "")
set.String("vuln-type", "os,library", "")
set.String("security-checks", "vuln", "")

View File

@@ -138,7 +138,7 @@ func (c *ReportOption) populateSecurityChecks() error {
func (c *ReportOption) forceListAllPkgs(logger *zap.SugaredLogger) bool {
if slices.Contains(supportedSbomFormats, c.Format) && !c.ListAllPkgs {
logger.Debugf("'cyclonedx', 'spdx', and 'spdx-json' automatically enables '--list-all-pkgs'.")
logger.Debugf("'github', 'cyclonedx', 'spdx', and 'spdx-json' automatically enables '--list-all-pkgs'.")
return true
}
return false

View File

@@ -11,6 +11,7 @@ import (
"go.uber.org/zap/zaptest/observer"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/report"
"github.com/aquasecurity/trivy/pkg/types"
)
@@ -78,7 +79,7 @@ func TestReportReportConfig_Init(t *testing.T) {
severities: "CRITICAL",
vulnType: "os,library",
securityChecks: "vuln",
Format: "cyclonedx",
Format: report.FormatCycloneDX,
listAllPksgs: true,
},
args: []string{"centos:7"},
@@ -86,7 +87,7 @@ func TestReportReportConfig_Init(t *testing.T) {
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
SecurityChecks: []string{types.SecurityCheckVulnerability},
Format: "cyclonedx",
Format: report.FormatCycloneDX,
Output: os.Stdout,
ListAllPkgs: true,
},
@@ -103,7 +104,7 @@ func TestReportReportConfig_Init(t *testing.T) {
},
args: []string{"centos:7"},
logs: []string{
"'cyclonedx', 'spdx', and 'spdx-json' automatically enables '--list-all-pkgs'.",
"'github', 'cyclonedx', 'spdx', and 'spdx-json' automatically enables '--list-all-pkgs'.",
"Severities: CRITICAL",
},
want: ReportOption{

View File

@@ -1,14 +1,15 @@
package option
import (
"github.com/urfave/cli/v2"
"go.uber.org/zap"
"golang.org/x/exp/slices"
"golang.org/x/xerrors"
"github.com/urfave/cli/v2"
"go.uber.org/zap"
"github.com/aquasecurity/trivy/pkg/report"
)
var supportedSbomFormats = []string{"cyclonedx", "spdx", "spdx-json"}
var supportedSbomFormats = []string{report.FormatCycloneDX, report.FormatSPDX, report.FormatSPDXJSON, report.FormatGitHub}
// SbomOption holds the options for SBOM generation
type SbomOption struct {

View File

@@ -60,10 +60,14 @@ func NewPackageURL(t string, metadata types.Metadata, pkg ftypes.Package) (Packa
qualifiers = append(qualifiers, qs...)
case packageurl.TypeDebian:
qualifiers = append(qualifiers, parseDeb(metadata.OS)...)
namespace = metadata.OS.Family
if metadata.OS != nil {
namespace = metadata.OS.Family
}
case string(analyzer.TypeApk): // TODO: replace with packageurl.TypeApk once they add it.
qualifiers = append(qualifiers, parseApk(metadata.OS)...)
namespace = metadata.OS.Family
if metadata.OS != nil {
namespace = metadata.OS.Family
}
case packageurl.TypeMaven:
namespace, name = parseMaven(name)
case packageurl.TypePyPi:
@@ -119,6 +123,10 @@ func parseOCI(metadata types.Metadata) (packageurl.PackageURL, error) {
}
func parseApk(fos *ftypes.OS) packageurl.Qualifiers {
if fos == nil {
return packageurl.Qualifiers{}
}
return packageurl.Qualifiers{
{
Key: "distro",
@@ -129,6 +137,11 @@ func parseApk(fos *ftypes.OS) packageurl.Qualifiers {
// ref. https://github.com/package-url/purl-spec/blob/a748c36ad415c8aeffe2b8a4a5d8a50d16d6d85f/PURL-TYPES.rst#deb
func parseDeb(fos *ftypes.OS) packageurl.Qualifiers {
if fos == nil {
return packageurl.Qualifiers{}
}
distro := fmt.Sprintf("%s-%s", fos.Family, fos.Name)
return packageurl.Qualifiers{
{
@@ -140,6 +153,10 @@ func parseDeb(fos *ftypes.OS) packageurl.Qualifiers {
// ref. https://github.com/package-url/purl-spec/blob/a748c36ad415c8aeffe2b8a4a5d8a50d16d6d85f/PURL-TYPES.rst#rpm
func parseRPM(fos *ftypes.OS, modularityLabel string) (string, packageurl.Qualifiers) {
if fos == nil {
return "", packageurl.Qualifiers{}
}
// SLES string has whitespace
family := fos.Family
if fos.Family == os.SLES {

178
pkg/report/github/github.go Normal file
View File

@@ -0,0 +1,178 @@
package github
import (
"encoding/json"
"fmt"
"io"
"os"
"strings"
"time"
"golang.org/x/xerrors"
ftypes "github.com/aquasecurity/fanal/types"
"github.com/aquasecurity/trivy/pkg/clock"
"github.com/aquasecurity/trivy/pkg/purl"
"github.com/aquasecurity/trivy/pkg/types"
)
const (
DirectRelationship string = "direct"
IndirectRelationship string = "indirect"
RuntimeScope string = "runtime"
DevelopmentScope string = "development"
)
type Package struct {
PackageUrl string `json:"package_url,omitempty"`
Relationship string `json:"relationship,omitempty"`
Dependencies []string `json:"dependencies,omitempty"`
Scope string `json:"scope,omitempty"`
Metadata Metadata `json:"metadata,omitempty"`
}
type File struct {
SrcLocation string `json:"source_location,omitempty"`
}
type Metadata map[string]interface{}
type Manifest struct {
Name string `json:"name,omitempty"`
File *File `json:"file,omitempty"`
Metadata Metadata `json:"metadata,omitempty"`
Resolved map[string]Package `json:"resolved,omitempty"`
}
type Job struct {
Correlator string `json:"correlator,omitempty"`
Id string `json:"id,omitempty"`
}
type Detector struct {
Name string `json:"name"`
Version string `json:"version"`
Url string `json:"url"`
}
type DependencySnapshot struct {
Version int `json:"version,omitempty"`
Detector Detector `json:"detector"`
Metadata Metadata `json:"metadata,omitempty"`
Ref string `json:"ref,omitempty"`
Sha string `json:"sha,omitempty"`
Job *Job `json:"job,omitempty"`
Scanned string `json:"scanned,omitempty"`
Manifests map[string]Manifest `json:"manifests,omitempty"`
}
// Writer generates JSON for GitHub Dependency Snapshots
type Writer struct {
Output io.Writer
Version string
}
func (w Writer) Write(report types.Report) error {
snapshot := &DependencySnapshot{}
//use now() method that can be overwritten while integration tests run
snapshot.Scanned = clock.Now().Format(time.RFC3339)
snapshot.Detector = Detector{
Name: "trivy",
Version: w.Version,
Url: "https://github.com/aquasecurity/trivy",
}
snapshot.Version = 0 // The version of the repository snapshot submission.
snapshot.Ref = os.Getenv("GITHUB_REF")
snapshot.Sha = os.Getenv("GITHUB_SHA")
snapshot.Job = &Job{
Correlator: fmt.Sprintf("%s_%s", os.Getenv("GITHUB_WORKFLOW"), os.Getenv("GITHUB_JOB")),
Id: os.Getenv("GITHUB_RUN_ID"),
}
snapshot.Metadata = getMetadata(report)
manifests := make(map[string]Manifest)
for _, result := range report.Results {
if result.Packages == nil {
continue
}
manifest := Manifest{}
manifest.Name = result.Type
//show path for languages only
if result.Class == types.ClassLangPkg {
manifest.File = &File{
SrcLocation: result.Target,
}
}
resolved := make(map[string]Package)
for _, pkg := range result.Packages {
var err error
githubPkg := Package{}
githubPkg.Scope = RuntimeScope
githubPkg.Relationship = getPkgRelationshipType(pkg)
githubPkg.Dependencies = getDependencies(result, pkg)
githubPkg.PackageUrl, err = buildPurl(result.Type, pkg)
if err != nil {
return xerrors.Errorf("unable to build purl for %s: %w", pkg.Name, err)
}
resolved[pkg.Name] = githubPkg
}
manifest.Resolved = resolved
manifests[result.Target] = manifest
}
snapshot.Manifests = manifests
output, err := json.MarshalIndent(snapshot, "", " ")
if err != nil {
return xerrors.Errorf("failed to marshal github dependency snapshots: %w", err)
}
if _, err = fmt.Fprint(w.Output, string(output)); err != nil {
return xerrors.Errorf("failed to write github dependency snapshots: %w", err)
}
return nil
}
func getMetadata(report types.Report) Metadata {
metadata := Metadata{}
if report.Metadata.RepoTags != nil {
metadata["aquasecurity:trivy:RepoTag"] = strings.Join(report.Metadata.RepoTags, ", ")
}
if report.Metadata.RepoDigests != nil {
metadata["aquasecurity:trivy:RepoDigest"] = strings.Join(report.Metadata.RepoDigests, ", ")
}
return metadata
}
func getDependencies(result types.Result, pkg ftypes.Package) []string {
for _, dep := range result.Dependencies {
if dep.ID == pkg.ID {
return dep.DependsOn
}
}
return []string{}
}
func getPkgRelationshipType(pkg ftypes.Package) string {
if pkg.Indirect {
return IndirectRelationship
}
return DirectRelationship
}
func buildPurl(t string, pkg ftypes.Package) (string, error) {
packageUrl, err := purl.NewPackageURL(t, types.Metadata{}, pkg)
if err != nil {
return "", xerrors.Errorf("purl error: %w", err)
}
return packageUrl.ToString(), nil
}

View File

@@ -0,0 +1,158 @@
package github_test
import (
"bytes"
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
ftypes "github.com/aquasecurity/fanal/types"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/report"
"github.com/aquasecurity/trivy/pkg/report/github"
"github.com/aquasecurity/trivy/pkg/types"
)
func TestWriter_Write(t *testing.T) {
tests := []struct {
name string
report types.Report
want map[string]github.Manifest
}{
{
name: "os packages",
report: types.Report{
SchemaVersion: 2,
ArtifactName: "alpine:3.14",
Results: types.Results{
{
Target: "yarn.lock",
Class: "lang-pkgs",
Type: "yarn",
Packages: []ftypes.Package{
{
Name: "@xtuc/ieee754",
Version: "1.2.0",
},
{
Name: "@xtuc/long",
Version: "4.2.2",
},
{
Name: "@xtuc/binaryen",
Version: "1.37.33",
Indirect: true,
},
},
Vulnerabilities: []types.DetectedVulnerability{
{
VulnerabilityID: "CVE-2020-0001",
PkgName: "foo",
InstalledVersion: "1.2.3",
FixedVersion: "3.4.5",
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-0001",
Vulnerability: dbTypes.Vulnerability{
Title: "foobar",
Description: "baz",
Severity: "HIGH",
},
},
},
},
},
},
want: map[string]github.Manifest{
"yarn.lock": {
Name: "yarn",
File: &github.File{
SrcLocation: "yarn.lock",
},
Resolved: map[string]github.Package{
"@xtuc/ieee754": {
PackageUrl: "pkg:npm/%40xtuc/ieee754@1.2.0",
Relationship: "direct",
Scope: "runtime",
},
"@xtuc/long": {
PackageUrl: "pkg:npm/%40xtuc/long@4.2.2",
Relationship: "direct",
Scope: "runtime",
},
"@xtuc/binaryen": {
PackageUrl: "pkg:npm/%40xtuc/binaryen@1.37.33",
Relationship: "indirect",
Scope: "runtime",
},
},
},
},
},
{
name: "maven",
report: types.Report{
SchemaVersion: 2,
ArtifactName: "my-java-app",
Results: types.Results{
{
Target: "pom.xml",
Class: "lang-pkgs",
Type: "pom",
Packages: []ftypes.Package{
{
Name: "com.google.code.gson:gson",
Version: "2.2.2",
},
{
Name: "net.sf.opencsv:opencsv",
Version: "2.3",
},
},
},
},
},
want: map[string]github.Manifest{
"pom.xml": {
Name: "pom",
File: &github.File{
SrcLocation: "pom.xml",
},
Resolved: map[string]github.Package{
"com.google.code.gson:gson": {
PackageUrl: "pkg:maven/com.google.code.gson/gson@2.2.2",
Relationship: "direct",
Scope: "runtime",
},
"net.sf.opencsv:opencsv": {
PackageUrl: "pkg:maven/net.sf.opencsv/opencsv@2.3",
Relationship: "direct",
Scope: "runtime",
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
jw := github.Writer{}
written := bytes.Buffer{}
jw.Output = &written
inputResults := tt.report
err := report.Write(inputResults, report.Option{
Format: "github",
Output: &written,
})
assert.NoError(t, err)
var got github.DependencySnapshot
err = json.Unmarshal(written.Bytes(), &got)
assert.NoError(t, err, "invalid github written")
assert.Equal(t, tt.want, got.Manifests, tt.name)
})
}
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/clock"
"github.com/aquasecurity/trivy/pkg/report"
"github.com/aquasecurity/trivy/pkg/types"
)
@@ -162,9 +163,8 @@ func TestReportWriter_Template(t *testing.T) {
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
report.Now = func() time.Time {
return time.Date(2020, 8, 10, 7, 28, 17, 958601, time.UTC)
}
clock.SetFakeTime(t, time.Date(2020, 8, 10, 7, 28, 17, 958601, time.UTC))
os.Setenv("AWS_ACCOUNT_ID", "123456789012")
got := bytes.Buffer{}
inputReport := types.Report{

View File

@@ -4,23 +4,29 @@ import (
"io"
"strings"
"sync"
"time"
"golang.org/x/xerrors"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/report/cyclonedx"
"github.com/aquasecurity/trivy/pkg/report/github"
"github.com/aquasecurity/trivy/pkg/report/spdx"
"github.com/aquasecurity/trivy/pkg/types"
)
const (
SchemaVersion = 2
)
// Now returns the current time
var Now = time.Now
FormatTable = "table"
FormatJSON = "json"
FormatTemplate = "template"
FormatSarif = "sarif"
FormatCycloneDX = "cyclonedx"
FormatSPDX = "spdx"
FormatSPDXJSON = "spdx-json"
FormatGitHub = "github"
)
type Option struct {
Format string
@@ -38,7 +44,7 @@ type Option struct {
func Write(report types.Report, option Option) error {
var writer Writer
switch option.Format {
case "table":
case FormatTable:
writer = &TableWriter{
Output: option.Output,
Severities: option.Severities,
@@ -46,14 +52,16 @@ func Write(report types.Report, option Option) error {
IncludeNonFailures: option.IncludeNonFailures,
Trace: option.Trace,
}
case "json":
case FormatJSON:
writer = &JSONWriter{Output: option.Output}
case "cyclonedx":
case FormatGitHub:
writer = &github.Writer{Output: option.Output, Version: option.AppVersion}
case FormatCycloneDX:
// TODO: support xml format option with cyclonedx writer
writer = cyclonedx.NewWriter(option.Output, option.AppVersion)
case "spdx", "spdx-json":
case FormatSPDX, FormatSPDXJSON:
writer = spdx.NewWriter(option.Output, option.AppVersion, option.Format)
case "template":
case FormatTemplate:
// We keep `sarif.tpl` template working for backward compatibility for a while.
if strings.HasPrefix(option.OutputTemplate, "@") && strings.HasSuffix(option.OutputTemplate, "sarif.tpl") {
log.Logger.Warn("Using `--template sarif.tpl` is deprecated. Please migrate to `--format sarif`. See https://github.com/aquasecurity/trivy/discussions/1571")
@@ -64,7 +72,7 @@ func Write(report types.Report, option Option) error {
if writer, err = NewTemplateWriter(option.Output, option.OutputTemplate); err != nil {
return xerrors.Errorf("failed to initialize template writer: %w", err)
}
case "sarif":
case FormatSarif:
writer = SarifWriter{Output: option.Output, Version: option.AppVersion}
default:
return xerrors.Errorf("unknown format: %v", option.Format)

View File

@@ -233,6 +233,7 @@ func (s Scanner) scanLibrary(apps []ftypes.Application, options types.ScanOption
}
if options.ListAllPackages {
libReport.Packages = app.Libraries
libReport.Dependencies = app.Dependencies
}
results = append(results, libReport)
}

View File

@@ -6,6 +6,7 @@ import (
v1 "github.com/google/go-containerregistry/pkg/v1" // nolint: goimports
ftypes "github.com/aquasecurity/fanal/types"
gdpTypes "github.com/aquasecurity/go-dep-parser/pkg/types"
)
// Report represents a scan result
@@ -48,6 +49,7 @@ type Result struct {
Class ResultClass `json:"Class,omitempty"`
Type string `json:"Type,omitempty"`
Packages []ftypes.Package `json:"Packages,omitempty"`
Dependencies []gdpTypes.Dependency `json:"Dependencies,omitempty"`
Vulnerabilities []DetectedVulnerability `json:"Vulnerabilities,omitempty"`
MisconfSummary *MisconfSummary `json:"MisconfSummary,omitempty"`
Misconfigurations []DetectedMisconfiguration `json:"Misconfigurations,omitempty"`