update trivy, and unsupport image scanning feature (#971)
* update trivy, fanal. unsupport image scanning * Update models/library.go Co-authored-by: Teppei Fukuda <teppei@elab.ic.i.u-tokyo.ac.jp> * add -no-progress flag to report/tui cmd * Display trivy vuln info to tui/report * add detection method to vulninfo detected by trivy * fix(uuid): change uuid lib to go-uuid #929 (#969) * update trivy, fanal. unsupport image scanning * Update models/library.go Co-authored-by: Teppei Fukuda <teppei@elab.ic.i.u-tokyo.ac.jp> * add -no-progress flag to report/tui cmd * Display trivy vuln info to tui/report * add detection method to vulninfo detected by trivy * unique ref links in TUI * download trivy DB only when lock file is specified in config.toml Co-authored-by: Teppei Fukuda <teppei@elab.ic.i.u-tokyo.ac.jp>
This commit is contained in:
@@ -3,7 +3,7 @@ package models
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/vulnsrc/vulnerability"
|
||||
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability"
|
||||
)
|
||||
|
||||
// CveContents has CveContent
|
||||
@@ -223,16 +223,18 @@ func NewCveContentType(name string) CveContentType {
|
||||
return WPVulnDB
|
||||
case "amazon":
|
||||
return Amazon
|
||||
case vulnerability.NodejsSecurityWg:
|
||||
return NodeSec
|
||||
case vulnerability.PythonSafetyDB:
|
||||
return PythonSec
|
||||
case vulnerability.RustSec:
|
||||
return RustSec
|
||||
case vulnerability.PhpSecurityAdvisories:
|
||||
return PhpSec
|
||||
case vulnerability.RubySec:
|
||||
return RubySec
|
||||
case "trivy":
|
||||
return Trivy
|
||||
// case vulnerability.NodejsSecurityWg:
|
||||
// return NodeSec
|
||||
// case vulnerability.PythonSafetyDB:
|
||||
// return PythonSec
|
||||
// case vulnerability.RustSec:
|
||||
// return RustSec
|
||||
// case vulnerability.PhpSecurityAdvisories:
|
||||
// return PhpSec
|
||||
// case vulnerability.RubySec:
|
||||
// return RubySec
|
||||
default:
|
||||
return Unknown
|
||||
}
|
||||
@@ -278,20 +280,23 @@ const (
|
||||
// WPVulnDB is WordPress
|
||||
WPVulnDB CveContentType = "wpvulndb"
|
||||
|
||||
// Trivy is Trivy
|
||||
Trivy CveContentType = "trivy"
|
||||
|
||||
// NodeSec : for JS
|
||||
NodeSec CveContentType = "node"
|
||||
// NodeSec CveContentType = "node"
|
||||
|
||||
// PythonSec : for PHP
|
||||
PythonSec CveContentType = "python"
|
||||
// // PythonSec : for PHP
|
||||
// PythonSec CveContentType = "python"
|
||||
|
||||
// PhpSec : for PHP
|
||||
PhpSec CveContentType = "php"
|
||||
// // PhpSec : for PHP
|
||||
// PhpSec CveContentType = "php"
|
||||
|
||||
// RubySec : for Ruby
|
||||
RubySec CveContentType = "ruby"
|
||||
// // RubySec : for Ruby
|
||||
// RubySec CveContentType = "ruby"
|
||||
|
||||
// RustSec : for Rust
|
||||
RustSec CveContentType = "rust"
|
||||
// // RustSec : for Rust
|
||||
// RustSec CveContentType = "rust"
|
||||
|
||||
// Unknown is Unknown
|
||||
Unknown CveContentType = "unknown"
|
||||
@@ -313,11 +318,12 @@ var AllCveContetTypes = CveContentTypes{
|
||||
SUSE,
|
||||
DebianSecurityTracker,
|
||||
WPVulnDB,
|
||||
NodeSec,
|
||||
PythonSec,
|
||||
PhpSec,
|
||||
RubySec,
|
||||
RustSec,
|
||||
Trivy,
|
||||
// NodeSec,
|
||||
// PythonSec,
|
||||
// PhpSec,
|
||||
// RubySec,
|
||||
// RustSec,
|
||||
}
|
||||
|
||||
// Except returns CveContentTypes except for given args
|
||||
|
||||
@@ -3,15 +3,35 @@ package models
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/scanner/library"
|
||||
"github.com/aquasecurity/trivy/pkg/vulnsrc/vulnerability"
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
trivyDBTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/library"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/future-architect/vuls/util"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/go-dep-parser/pkg/types"
|
||||
// "github.com/aquasecurity/go-dep-parser/pkg/types"
|
||||
"github.com/knqyf263/go-version"
|
||||
)
|
||||
|
||||
// LibraryScanners is an array of LibraryScanner
|
||||
type LibraryScanners []LibraryScanner
|
||||
|
||||
// Find : find by name
|
||||
func (lss LibraryScanners) Find(name string) map[string]types.Library {
|
||||
filtered := map[string]types.Library{}
|
||||
for _, ls := range lss {
|
||||
for _, lib := range ls.Libs {
|
||||
if lib.Name == name {
|
||||
filtered[ls.Path] = lib
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
// LibraryScanner has libraries information
|
||||
type LibraryScanner struct {
|
||||
Path string
|
||||
@@ -20,17 +40,11 @@ type LibraryScanner struct {
|
||||
|
||||
// Scan : scan target library
|
||||
func (s LibraryScanner) Scan() ([]VulnInfo, error) {
|
||||
scanner := library.NewScanner(filepath.Base(string(s.Path)))
|
||||
scanner := library.DriverFactory{}.NewDriver(filepath.Base(string(s.Path)))
|
||||
if scanner == nil {
|
||||
return nil, xerrors.New("unknown file type")
|
||||
}
|
||||
|
||||
util.Log.Info("Updating library db...")
|
||||
err := scanner.UpdateDB()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to update %s advisories: %w", scanner.Type(), err)
|
||||
}
|
||||
|
||||
var vulnerabilities []VulnInfo
|
||||
for _, pkg := range s.Libs {
|
||||
v, err := version.NewVersion(pkg.Version)
|
||||
@@ -43,6 +57,9 @@ func (s LibraryScanner) Scan() ([]VulnInfo, error) {
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to detect %s vulnerabilities: %w", scanner.Type(), err)
|
||||
}
|
||||
if len(tvulns) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
vulns := s.convertFanalToVuln(tvulns)
|
||||
vulnerabilities = append(vulnerabilities, vulns...)
|
||||
@@ -51,25 +68,27 @@ func (s LibraryScanner) Scan() ([]VulnInfo, error) {
|
||||
return vulnerabilities, nil
|
||||
}
|
||||
|
||||
func (s LibraryScanner) convertFanalToVuln(tvulns []vulnerability.DetectedVulnerability) (vulns []VulnInfo) {
|
||||
func (s LibraryScanner) convertFanalToVuln(tvulns []types.DetectedVulnerability) (vulns []VulnInfo) {
|
||||
for _, tvuln := range tvulns {
|
||||
vinfo, _ := s.getVulnDetail(tvuln)
|
||||
vinfo, err := s.getVulnDetail(tvuln)
|
||||
if err != nil {
|
||||
util.Log.Debugf("failed to getVulnDetail. err: %s, tvun: %#v", err, tvuln)
|
||||
continue
|
||||
}
|
||||
vulns = append(vulns, vinfo)
|
||||
}
|
||||
return vulns
|
||||
}
|
||||
|
||||
func (s LibraryScanner) getVulnDetail(tvuln vulnerability.DetectedVulnerability) (vinfo VulnInfo, err error) {
|
||||
details, err := vulnerability.Get(tvuln.VulnerabilityID)
|
||||
func (s LibraryScanner) getVulnDetail(tvuln types.DetectedVulnerability) (vinfo VulnInfo, err error) {
|
||||
vul, err := db.Config{}.GetVulnerability(tvuln.VulnerabilityID)
|
||||
if err != nil {
|
||||
return vinfo, err
|
||||
} else if len(details) == 0 {
|
||||
return vinfo, xerrors.Errorf("Unknown vulnID : %s", tvuln.VulnerabilityID)
|
||||
}
|
||||
vinfo.CveID = tvuln.VulnerabilityID
|
||||
vinfo.CveContents = getCveContents(details)
|
||||
if tvuln.FixedVersion != "" {
|
||||
|
||||
vinfo.CveID = tvuln.VulnerabilityID
|
||||
vinfo.CveContents = getCveContents(tvuln.VulnerabilityID, vul)
|
||||
if tvuln.FixedVersion != "" {
|
||||
vinfo.LibraryFixedIns = []LibraryFixedIn{
|
||||
{
|
||||
Key: s.GetLibraryKey(),
|
||||
@@ -81,38 +100,22 @@ func (s LibraryScanner) getVulnDetail(tvuln vulnerability.DetectedVulnerability)
|
||||
return vinfo, nil
|
||||
}
|
||||
|
||||
func getCveContents(details map[string]vulnerability.Vulnerability) (contents map[CveContentType]CveContent) {
|
||||
func getCveContents(cveID string, vul trivyDBTypes.Vulnerability) (contents map[CveContentType]CveContent) {
|
||||
contents = map[CveContentType]CveContent{}
|
||||
for source, detail := range details {
|
||||
refs := []Reference{}
|
||||
for _, refURL := range detail.References {
|
||||
refs = append(refs, Reference{Source: refURL, Link: refURL})
|
||||
}
|
||||
|
||||
content := CveContent{
|
||||
Type: NewCveContentType(source),
|
||||
CveID: detail.ID,
|
||||
Title: detail.Title,
|
||||
Summary: detail.Description,
|
||||
Cvss3Score: detail.CvssScoreV3,
|
||||
Cvss3Severity: string(detail.SeverityV3),
|
||||
Cvss2Score: detail.CvssScore,
|
||||
Cvss2Severity: string(detail.Severity),
|
||||
References: refs,
|
||||
|
||||
//SourceLink string `json:"sourceLink"`
|
||||
//Cvss2Vector string `json:"cvss2Vector"`
|
||||
//Cvss3Vector string `json:"cvss3Vector"`
|
||||
//Cvss3Severity string `json:"cvss3Severity"`
|
||||
//Cpes []Cpe `json:"cpes,omitempty"`
|
||||
//CweIDs []string `json:"cweIDs,omitempty"`
|
||||
//Published time.Time `json:"published"`
|
||||
//LastModified time.Time `json:"lastModified"`
|
||||
//Mitigation string `json:"mitigation"` // RedHat API
|
||||
//Optional map[string]string `json:"optional,omitempty"`
|
||||
}
|
||||
contents[NewCveContentType(source)] = content
|
||||
refs := []Reference{}
|
||||
for _, refURL := range vul.References {
|
||||
refs = append(refs, Reference{Source: "trivy", Link: refURL})
|
||||
}
|
||||
|
||||
content := CveContent{
|
||||
Type: Trivy,
|
||||
CveID: cveID,
|
||||
Title: vul.Title,
|
||||
Summary: vul.Description,
|
||||
Cvss3Severity: string(vul.Severity),
|
||||
References: refs,
|
||||
}
|
||||
contents[Trivy] = content
|
||||
return contents
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
godeptypes "github.com/aquasecurity/go-dep-parser/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/db"
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/utils"
|
||||
)
|
||||
|
||||
func TestScan(t *testing.T) {
|
||||
var tests = []struct {
|
||||
path string
|
||||
pkgs []godeptypes.Library
|
||||
pkgs []types.Library
|
||||
}{
|
||||
{
|
||||
path: "app/package-lock.json",
|
||||
pkgs: []godeptypes.Library{
|
||||
pkgs: []types.Library{
|
||||
{
|
||||
Name: "jquery",
|
||||
Version: "2.2.4",
|
||||
@@ -32,7 +34,8 @@ func TestScan(t *testing.T) {
|
||||
t.Errorf("trivy logger failed")
|
||||
}
|
||||
|
||||
if err := db.Init(); err != nil {
|
||||
cacheDir := utils.DefaultCacheDir()
|
||||
if err := db.Init(cacheDir); err != nil {
|
||||
t.Errorf("trivy db.Init failed")
|
||||
}
|
||||
for _, v := range tests {
|
||||
@@ -50,3 +53,94 @@ func TestScan(t *testing.T) {
|
||||
}
|
||||
db.Close()
|
||||
}
|
||||
|
||||
func TestLibraryScanners_Find(t *testing.T) {
|
||||
type args struct {
|
||||
name string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
lss LibraryScanners
|
||||
args args
|
||||
want map[string]types.Library
|
||||
}{
|
||||
{
|
||||
name: "single file",
|
||||
lss: LibraryScanners{
|
||||
{
|
||||
Path: "/pathA",
|
||||
Libs: []types.Library{
|
||||
{
|
||||
Name: "libA",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{"libA"},
|
||||
want: map[string]types.Library{
|
||||
"/pathA": {
|
||||
Name: "libA",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multi file",
|
||||
lss: LibraryScanners{
|
||||
{
|
||||
Path: "/pathA",
|
||||
Libs: []types.Library{
|
||||
{
|
||||
Name: "libA",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Path: "/pathB",
|
||||
Libs: []types.Library{
|
||||
{
|
||||
Name: "libA",
|
||||
Version: "1.0.5",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{"libA"},
|
||||
want: map[string]types.Library{
|
||||
"/pathA": {
|
||||
Name: "libA",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"/pathB": {
|
||||
Name: "libA",
|
||||
Version: "1.0.5",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "miss",
|
||||
lss: LibraryScanners{
|
||||
{
|
||||
Path: "/pathA",
|
||||
Libs: []types.Library{
|
||||
{
|
||||
Name: "libA",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{"libB"},
|
||||
want: map[string]types.Library{},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.lss.Find(tt.args.name); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("LibraryScanners.Find() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ type ScanResult struct {
|
||||
Family string `json:"family"`
|
||||
Release string `json:"release"`
|
||||
Container Container `json:"container"`
|
||||
Image Image `json:"image"`
|
||||
Platform Platform `json:"platform"`
|
||||
IPv4Addrs []string `json:"ipv4Addrs,omitempty"` // only global unicast address (https://golang.org/pkg/net/#IP.IsGlobalUnicast)
|
||||
IPv6Addrs []string `json:"ipv6Addrs,omitempty"` // only global unicast address (https://golang.org/pkg/net/#IP.IsGlobalUnicast)
|
||||
@@ -49,7 +48,7 @@ type ScanResult struct {
|
||||
Packages Packages `json:"packages"`
|
||||
SrcPackages SrcPackages `json:",omitempty"`
|
||||
WordPressPackages *WordPressPackages `json:",omitempty"`
|
||||
LibraryScanners []LibraryScanner `json:"libScanners"`
|
||||
LibraryScanners LibraryScanners `json:"libraries,omitempty"`
|
||||
CweDict CweDict `json:"cweDict,omitempty"`
|
||||
Optional map[string]interface{} `json:",omitempty"`
|
||||
Config struct {
|
||||
@@ -435,11 +434,6 @@ func (r ScanResult) IsContainer() bool {
|
||||
return 0 < len(r.Container.ContainerID)
|
||||
}
|
||||
|
||||
// IsImage returns whether this ServerInfo is about container
|
||||
func (r ScanResult) IsImage() bool {
|
||||
return 0 < len(r.Image.Name)
|
||||
}
|
||||
|
||||
// IsDeepScanMode checks if the scan mode is deep scan mode.
|
||||
func (r ScanResult) IsDeepScanMode() bool {
|
||||
for _, s := range r.Config.Scan.Servers {
|
||||
@@ -461,13 +455,6 @@ type Container struct {
|
||||
UUID string `json:"uuid"`
|
||||
}
|
||||
|
||||
// Image has Container information
|
||||
type Image struct {
|
||||
Name string `json:"name"`
|
||||
Tag string `json:"tag"`
|
||||
Digest string `json:"digest"`
|
||||
}
|
||||
|
||||
// Platform has platform information
|
||||
type Platform struct {
|
||||
Name string `json:"name"` // aws or azure or gcp or other...
|
||||
|
||||
@@ -200,6 +200,14 @@ type GitHubSecurityAlert struct {
|
||||
// LibraryFixedIns is a list of Library's FixedIn
|
||||
type LibraryFixedIns []LibraryFixedIn
|
||||
|
||||
// Names return a slice of names
|
||||
func (lfs LibraryFixedIns) Names() (names []string) {
|
||||
for _, lf := range lfs {
|
||||
names = append(names, lf.Name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// WpPackageFixStats is a list of WpPackageFixStatus
|
||||
type WpPackageFixStats []WpPackageFixStatus
|
||||
|
||||
@@ -237,7 +245,7 @@ func (v VulnInfo) Titles(lang, myFamily string) (values []CveContentStr) {
|
||||
values = append(values, CveContentStr{RedHatAPI, cont.Title})
|
||||
}
|
||||
|
||||
order := CveContentTypes{Nvd, NvdXML, NewCveContentType(myFamily)}
|
||||
order := CveContentTypes{Trivy, Nvd, NvdXML, NewCveContentType(myFamily)}
|
||||
order = append(order, AllCveContetTypes.Except(append(order, Jvn)...)...)
|
||||
for _, ctype := range order {
|
||||
// Only JVN has meaningful title. so return first 100 char of summary
|
||||
@@ -277,7 +285,7 @@ func (v VulnInfo) Summaries(lang, myFamily string) (values []CveContentStr) {
|
||||
}
|
||||
}
|
||||
|
||||
order := CveContentTypes{NewCveContentType(myFamily), Nvd, NvdXML}
|
||||
order := CveContentTypes{Trivy, NewCveContentType(myFamily), Nvd, NvdXML}
|
||||
order = append(order, AllCveContetTypes.Except(append(order, Jvn)...)...)
|
||||
for _, ctype := range order {
|
||||
if cont, found := v.CveContents[ctype]; found && 0 < len(cont.Summary) {
|
||||
@@ -415,6 +423,18 @@ func (v VulnInfo) Cvss3Scores() (values []CveContentCvss) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if cont, found := v.CveContents[Trivy]; found && cont.Cvss3Severity != "" {
|
||||
values = append(values, CveContentCvss{
|
||||
Type: Trivy,
|
||||
Value: Cvss{
|
||||
Type: CVSS3,
|
||||
Score: severityToV2ScoreRoughly(cont.Cvss3Severity),
|
||||
Severity: strings.ToUpper(cont.Cvss3Severity),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -855,6 +875,9 @@ const (
|
||||
// DebianSecurityTrackerMatchStr is a String representation of DebianSecurityTrackerMatch
|
||||
DebianSecurityTrackerMatchStr = "DebianSecurityTrackerMatch"
|
||||
|
||||
// TrivyMatchStr is a String representation of Trivy
|
||||
TrivyMatchStr = "TrivyMatch"
|
||||
|
||||
// ChangelogExactMatchStr is a String representation of ChangelogExactMatch
|
||||
ChangelogExactMatchStr = "ChangelogExactMatch"
|
||||
|
||||
@@ -893,6 +916,9 @@ var (
|
||||
// DebianSecurityTrackerMatch ranking how confident the CVE-ID was deteted correctly
|
||||
DebianSecurityTrackerMatch = Confidence{100, DebianSecurityTrackerMatchStr, 0}
|
||||
|
||||
// TrivyMatch ranking how confident the CVE-ID was deteted correctly
|
||||
TrivyMatch = Confidence{100, TrivyMatchStr, 0}
|
||||
|
||||
// ChangelogExactMatch is a ranking how confident the CVE-ID was deteted correctly
|
||||
ChangelogExactMatch = Confidence{95, ChangelogExactMatchStr, 3}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user