Skip to content

Commit

Permalink
Add GHSA support (#467)
Browse files Browse the repository at this point in the history
* Change library advisory use github security advisory

* Add java scanner

* Add multi vulnsrc support

* Fix null pointer exception

* Add ghsa mock test

* Delete nuget & java

* Update README

* Fix bug

* refactor: add ghsa

* refactor: Add multi scanner in driver.go

* fix go.mod

* Add scanner.go

* Add parse lockfile

* unexport Driver & delete parse lockfile

* Fix scanner struct

* refactor: scanner -> advisory

* Add Driver

* delete Driver interface

* Add new drivers

* delete types.go

* Fix review

* Merge driver.go ← advisory.go

* Change NewDriver interface

Co-authored-by: Teppei Fukuda <[email protected]>
  • Loading branch information
masahiro331 and knqyf263 authored May 30, 2020
1 parent 1218e11 commit 03ad8a3
Show file tree
Hide file tree
Showing 11 changed files with 221 additions and 242 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1411,12 +1411,16 @@ Trivy scans a tar image with the following format.
### Data source
- PHP
- https://github.com/FriendsOfPHP/security-advisories
- https://github.com/advisories?query=ecosystem%3Acomposer
- Python
- https://github.com/pyupio/safety-db
- https://github.com/advisories?query=ecosystem%3Apip
- Ruby
- https://github.com/rubysec/ruby-advisory-db
- https://github.com/advisories?query=ecosystem%3Arubygems
- Node.js
- https://github.com/nodejs/security-wg
- https://github.com/advisories?query=ecosystem%3Anpm
- Rust
- https://github.com/RustSec/advisory-db

Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
package bundler

import (
"os"
"strings"

"github.com/knqyf263/go-version"
"golang.org/x/xerrors"

"github.com/aquasecurity/go-dep-parser/pkg/bundler"
ptypes "github.com/aquasecurity/go-dep-parser/pkg/types"
bundlerSrc "github.com/aquasecurity/trivy-db/pkg/vulnsrc/bundler"
"github.com/aquasecurity/trivy/pkg/scanner/utils"
"github.com/aquasecurity/trivy/pkg/types"
)

const (
scannerType = "bundler"
)

var (
platformReplacer = strings.NewReplacer(
"-java", "+java",
Expand All @@ -34,7 +27,7 @@ type VulnSrc interface {
Get(pkgName string) ([]bundlerSrc.Advisory, error)
}

type Scanner struct {
type Advisory struct {
vs VulnSrc
}

Expand All @@ -45,16 +38,16 @@ func massageLockFileVersion(version string) string {
return platformReplacer.Replace(version)
}

func NewScanner() *Scanner {
return &Scanner{
func NewAdvisory() *Advisory {
return &Advisory{
vs: bundlerSrc.NewVulnSrc(),
}
}

func (s *Scanner) Detect(pkgName string, pkgVer *version.Version) ([]types.DetectedVulnerability, error) {
advisories, err := s.vs.Get(pkgName)
func (a *Advisory) DetectVulnerabilities(pkgName string, pkgVer *version.Version) ([]types.DetectedVulnerability, error) {
advisories, err := a.vs.Get(pkgName)
if err != nil {
return nil, xerrors.Errorf("failed to get %s advisories: %w", s.Type(), err)
return nil, xerrors.Errorf("failed to get bundler advisories: %w", err)
}

var vulns []types.DetectedVulnerability
Expand All @@ -76,20 +69,3 @@ func (s *Scanner) Detect(pkgName string, pkgVer *version.Version) ([]types.Detec
}
return vulns, nil
}

func (s *Scanner) ParseLockfile(f *os.File) ([]ptypes.Library, error) {
libs, err := bundler.Parse(f)
if err != nil {
return nil, xerrors.Errorf("invalid Gemfile.lock format: %w", err)
}

for _, lib := range libs {
lib.Version = massageLockFileVersion(lib.Version)
}

return libs, nil
}

func (s *Scanner) Type() string {
return scannerType
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func TestScanner_Detect(t *testing.T) {
PatchedVersions: []string{">= 1.9.26"},
},
}, nil)
s := Scanner{
s := Advisory{
vs: mockVulnSrc,
}

Expand All @@ -55,7 +55,7 @@ func TestScanner_Detect(t *testing.T) {

v, _ := version.NewVersion(versionStr)

vulns, err := s.Detect("ffi", v)
vulns, err := s.DetectVulnerabilities("ffi", v)

assert.Nil(t, err)
assert.Equal(t, 1, len(vulns))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,30 @@
package cargo

import (
"os"
"strings"

"github.com/aquasecurity/trivy/pkg/types"

"github.com/aquasecurity/go-dep-parser/pkg/cargo"
ptypes "github.com/aquasecurity/go-dep-parser/pkg/types"
cargoSrc "github.com/aquasecurity/trivy-db/pkg/vulnsrc/cargo"
"github.com/aquasecurity/trivy/pkg/scanner/utils"
"github.com/knqyf263/go-version"
"golang.org/x/xerrors"
)

const (
scannerType = "cargo"
)

type Scanner struct {
type Advisory struct {
vs cargoSrc.VulnSrc
}

func NewScanner() *Scanner {
return &Scanner{
func NewAdvisory() *Advisory {
return &Advisory{
vs: cargoSrc.NewVulnSrc(),
}
}

func (s *Scanner) Detect(pkgName string, pkgVer *version.Version) ([]types.DetectedVulnerability, error) {
func (s *Advisory) DetectVulnerabilities(pkgName string, pkgVer *version.Version) ([]types.DetectedVulnerability, error) {
advisories, err := s.vs.Get(pkgName)
if err != nil {
return nil, xerrors.Errorf("failed to get %s advisories: %w", s.Type(), err)
return nil, xerrors.Errorf("failed to get cargo advisories: %w", err)
}

var vulns []types.DetectedVulnerability
Expand All @@ -50,15 +43,3 @@ func (s *Scanner) Detect(pkgName string, pkgVer *version.Version) ([]types.Detec
}
return vulns, nil
}

func (s *Scanner) ParseLockfile(f *os.File) ([]ptypes.Library, error) {
libs, err := cargo.Parse(f)
if err != nil {
return nil, xerrors.Errorf("invalid Cargo.lock format: %w", err)
}
return libs, nil
}

func (s *Scanner) Type() string {
return scannerType
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,32 @@ package composer

import (
"fmt"
"os"
"strings"

"github.com/aquasecurity/trivy/pkg/types"

"golang.org/x/xerrors"

"github.com/aquasecurity/go-dep-parser/pkg/composer"
ptypes "github.com/aquasecurity/go-dep-parser/pkg/types"
composerSrc "github.com/aquasecurity/trivy-db/pkg/vulnsrc/composer"
"github.com/aquasecurity/trivy/pkg/scanner/utils"
"github.com/knqyf263/go-version"
)

const (
scannerType = "composer"
)

type Scanner struct {
type Advisory struct {
vs composerSrc.VulnSrc
}

func NewScanner() *Scanner {
return &Scanner{
func NewAdvisory() *Advisory {
return &Advisory{
vs: composerSrc.NewVulnSrc(),
}
}

func (s *Scanner) Detect(pkgName string, pkgVer *version.Version) ([]types.DetectedVulnerability, error) {
func (s *Advisory) DetectVulnerabilities(pkgName string, pkgVer *version.Version) ([]types.DetectedVulnerability, error) {
ref := fmt.Sprintf("composer://%s", pkgName)
advisories, err := s.vs.Get(ref)
if err != nil {
return nil, xerrors.Errorf("failed to get %s advisories: %w", s.Type(), err)
return nil, xerrors.Errorf("failed to get composer advisories: %w", err)
}

var vulns []types.DetectedVulnerability
Expand Down Expand Up @@ -64,14 +57,3 @@ func (s *Scanner) Detect(pkgName string, pkgVer *version.Version) ([]types.Detec
}
return vulns, nil
}

func (s *Scanner) ParseLockfile(f *os.File) ([]ptypes.Library, error) {
libs, err := composer.Parse(f)
if err != nil {
return nil, xerrors.Errorf("invalid composer.lock format: %w", err)
}
return libs, nil
}
func (s *Scanner) Type() string {
return scannerType
}
6 changes: 3 additions & 3 deletions pkg/detector/library/detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ func NewDetector(factory Factory) Detector {

func (d Detector) Detect(_, filePath string, _ time.Time, pkgs []ftypes.LibraryInfo) ([]types.DetectedVulnerability, error) {
log.Logger.Debugf("Detecting library vulnerabilities, path: %s", filePath)
driver := d.driverFactory.NewDriver(filepath.Base(filePath))
if driver == nil {
return nil, xerrors.New("unknown file type")
driver, err := d.driverFactory.NewDriver(filepath.Base(filePath))
if err != nil {
return nil, xerrors.Errorf("failed to new driver: %w", err)
}

vulns, err := detect(driver, pkgs)
Expand Down
100 changes: 81 additions & 19 deletions pkg/detector/library/driver.go
Original file line number Diff line number Diff line change
@@ -1,50 +1,112 @@
package library

import (
"os"
"fmt"

ptypes "github.com/aquasecurity/go-dep-parser/pkg/types"
"github.com/aquasecurity/fanal/analyzer/library"
ecosystem "github.com/aquasecurity/trivy-db/pkg/vulnsrc/ghsa"
"github.com/aquasecurity/trivy/pkg/detector/library/bundler"
"github.com/aquasecurity/trivy/pkg/detector/library/cargo"
"github.com/aquasecurity/trivy/pkg/detector/library/composer"
"github.com/aquasecurity/trivy/pkg/detector/library/ghsa"
"github.com/aquasecurity/trivy/pkg/detector/library/node"
"github.com/aquasecurity/trivy/pkg/detector/library/python"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/knqyf263/go-version"
"golang.org/x/xerrors"
)

type Driver interface {
ParseLockfile(*os.File) ([]ptypes.Library, error)
Detect(string, *version.Version) ([]types.DetectedVulnerability, error)
Type() string
type Factory interface {
NewDriver(filename string) (Driver, error)
}

type Factory interface {
NewDriver(filename string) Driver
type advisory interface {
DetectVulnerabilities(string, *version.Version) ([]types.DetectedVulnerability, error)
}

type DriverFactory struct{}

func (d DriverFactory) NewDriver(filename string) Driver {
func (d DriverFactory) NewDriver(filename string) (Driver, error) {
// TODO: use DI
var scanner Driver
var driver Driver
switch filename {
case "Gemfile.lock":
scanner = bundler.NewScanner()
driver = NewBundlerDriver()
case "Cargo.lock":
scanner = cargo.NewScanner()
driver = NewCargoDriver()
case "composer.lock":
scanner = composer.NewScanner()
driver = NewComposerDriver()
case "package-lock.json":
scanner = node.NewScanner(node.ScannerTypeNpm)
driver = NewNpmDriver()
case "yarn.lock":
scanner = node.NewScanner(node.ScannerTypeYarn)
driver = NewYarnDriver()
case "Pipfile.lock":
scanner = python.NewScanner(python.ScannerTypePipenv)
driver = NewPipenvDriver()
case "poetry.lock":
scanner = python.NewScanner(python.ScannerTypePoetry)
driver = NewPoetryDriver()
default:
return nil
return Driver{}, xerrors.New(fmt.Sprintf("unsupport filename %s", filename))
}
return scanner
return driver, nil
}

type Driver struct {
pkgManager string
advisories []advisory
}

func NewDriver(p string, advisories ...advisory) Driver {
return Driver{pkgManager: p, advisories: advisories}
}

func (driver *Driver) Detect(pkgName string, pkgVer *version.Version) ([]types.DetectedVulnerability, error) {
var detectedVulnerabilities []types.DetectedVulnerability
uniqVulnIdMap := make(map[string]struct{})
for _, d := range driver.advisories {
vulns, err := d.DetectVulnerabilities(pkgName, pkgVer)
if err != nil {
return nil, xerrors.Errorf("failed to detect vulnerabilities: %w", err)
}
for _, vuln := range vulns {
if _, ok := uniqVulnIdMap[vuln.VulnerabilityID]; ok {
continue
}
uniqVulnIdMap[vuln.VulnerabilityID] = struct{}{}
detectedVulnerabilities = append(detectedVulnerabilities, vuln)
}
}

return detectedVulnerabilities, nil
}

func NewBundlerDriver() Driver {
return NewDriver(library.Bundler, ghsa.NewAdvisory(ecosystem.Rubygems), bundler.NewAdvisory())
}

func NewComposerDriver() Driver {
return NewDriver(library.Composer, ghsa.NewAdvisory(ecosystem.Composer), composer.NewAdvisory())
}

func NewCargoDriver() Driver {
return NewDriver(library.Cargo, cargo.NewAdvisory())
}

func NewNpmDriver() Driver {
return NewDriver(library.Npm, ghsa.NewAdvisory(ecosystem.Npm), node.NewAdvisory())
}

func NewYarnDriver() Driver {
return NewDriver(library.Yarn, ghsa.NewAdvisory(ecosystem.Npm), node.NewAdvisory())
}

func NewPipenvDriver() Driver {
return NewDriver(library.Pipenv, ghsa.NewAdvisory(ecosystem.Pip), python.NewAdvisory())
}

func NewPoetryDriver() Driver {
return NewDriver(library.Poetry, ghsa.NewAdvisory(ecosystem.Pip), python.NewAdvisory())
}

func (d *Driver) Type() string {
return d.pkgManager
}
Loading

0 comments on commit 03ad8a3

Please sign in to comment.