mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-22 07:10:41 -08:00
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:
@@ -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{}{}
|
||||
})
|
||||
|
||||
91
integration/testdata/alpine-310.gsbom.golden
vendored
Normal file
91
integration/testdata/alpine-310.gsbom.golden
vendored
Normal 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
23
pkg/clock/clock.go
Normal 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()
|
||||
}
|
||||
@@ -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"},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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", "")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)...)
|
||||
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)...)
|
||||
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
178
pkg/report/github/github.go
Normal 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
|
||||
}
|
||||
158
pkg/report/github/github_test.go
Normal file
158
pkg/report/github/github_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
Reference in New Issue
Block a user