Skip to content

Commit

Permalink
Support .trivyignore
Browse files Browse the repository at this point in the history
  • Loading branch information
knqyf263 committed May 13, 2019
1 parent 1827d3d commit 27d776c
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 146 deletions.
10 changes: 9 additions & 1 deletion pkg/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,19 @@ func Run(c *cli.Context) (err error) {
if filePath == "" {
imageName = args[0]
}
results, err := scanner.ScanImage(imageName, filePath, severities, ignoreUnfixed)
vulns, err := scanner.ScanImage(imageName, filePath)
if err != nil {
return xerrors.Errorf("error in image scan: %w", err)
}

var results report.Results
for path, vuln := range vulns {
results = append(results, report.Result{
FileName: path,
Vulnerabilities: vulnerability.FillAndFilter(vuln, severities, ignoreUnfixed),
})
}

var writer report.Writer
switch c.String("format") {
case "table":
Expand Down
157 changes: 12 additions & 145 deletions pkg/scanner/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,28 @@ import (
"flag"
"fmt"
"os"
"sort"

"golang.org/x/crypto/ssh/terminal"

"github.com/genuinetools/reg/registry"

"github.com/knqyf263/trivy/pkg/log"

"github.com/knqyf263/trivy/pkg/report"

"github.com/knqyf263/trivy/pkg/scanner/library"

"github.com/knqyf263/trivy/pkg/vulnsrc/vulnerability"

"github.com/knqyf263/trivy/pkg/scanner/ospkg"

"golang.org/x/crypto/ssh/terminal"
"golang.org/x/xerrors"

"github.com/knqyf263/fanal/analyzer"
"github.com/knqyf263/fanal/extractor"
)

var (
sources = []string{vulnerability.Nvd, vulnerability.RedHat, vulnerability.Debian,
vulnerability.DebianOVAL, vulnerability.Alpine, vulnerability.RubySec, vulnerability.RustSec, vulnerability.PhpSecurityAdvisories,
vulnerability.NodejsSecurityWg, vulnerability.PythonSafetyDB}
)

func ScanImage(imageName, filePath string, severities []vulnerability.Severity, ignoreUnfixed bool) (report.Results, error) {
var results report.Results
func ScanImage(imageName, filePath string) (map[string][]vulnerability.DetectedVulnerability, error) {
var err error
results := map[string][]vulnerability.DetectedVulnerability{}
ctx := context.Background()

image, err := registry.ParseImage(imageName)
Expand Down Expand Up @@ -73,67 +65,29 @@ func ScanImage(imageName, filePath string, severities []vulnerability.Severity,
return nil, xerrors.Errorf("failed to scan image: %w", err)

}

results = append(results, report.Result{
FileName: fmt.Sprintf("%s (%s %s)", target, osFamily, osVersion),
Vulnerabilities: processVulnerabilties(osVulns, severities, ignoreUnfixed),
})
imageDetail := fmt.Sprintf("%s (%s %s)", target, osFamily, osVersion)
results[imageDetail] = osVulns

libVulns, err := library.Scan(files)
if err != nil {
return nil, xerrors.Errorf("failed to scan libraries: %w", err)
}
for path, vulns := range libVulns {
results = append(results, report.Result{
FileName: path,
Vulnerabilities: processVulnerabilties(vulns, severities, ignoreUnfixed),
})
results[path] = vulns
}

return results, nil
}

func ScanFile(f *os.File, severities []vulnerability.Severity, ignoreUnfixed bool) (report.Result, error) {
func ScanFile(f *os.File) (map[string][]vulnerability.DetectedVulnerability, error) {
vulns, err := library.ScanFile(f)
if err != nil {
return report.Result{}, xerrors.Errorf("failed to scan libraries in file: %w", err)
}
result := report.Result{
FileName: f.Name(),
Vulnerabilities: processVulnerabilties(vulns, severities, ignoreUnfixed),
return nil, xerrors.Errorf("failed to scan libraries in file: %w", err)
}
return result, nil
}

func processVulnerabilties(vulns []vulnerability.DetectedVulnerability, severities []vulnerability.Severity, ignoreUnfixed bool) []vulnerability.DetectedVulnerability {
var vulnerabilities []vulnerability.DetectedVulnerability
for _, vuln := range vulns {
sev, title, description, references := getDetail(vuln.VulnerabilityID)

// Filter vulnerabilities by severity
for _, s := range severities {
if s == sev {
vuln.Severity = fmt.Sprint(sev)
vuln.Title = title
vuln.Description = description
vuln.References = references

// Ignore unfixed vulnerabilities
if ignoreUnfixed && vuln.FixedVersion == "" {
continue
}
vulnerabilities = append(vulnerabilities, vuln)
break
}
}
results := map[string][]vulnerability.DetectedVulnerability{
f.Name(): vulns,
}
sort.Slice(vulnerabilities, func(i, j int) bool {
if vulnerabilities[i].PkgName != vulnerabilities[j].PkgName {
return vulnerabilities[i].PkgName < vulnerabilities[j].PkgName
}
return vulnerability.CompareSeverityString(vulnerabilities[j].Severity, vulnerabilities[i].Severity)
})
return vulnerabilities
return results, nil
}

func openStream(path string) (*os.File, error) {
Expand All @@ -147,90 +101,3 @@ func openStream(path string) (*os.File, error) {
}
return os.Open(path)
}

func getDetail(vulnID string) (vulnerability.Severity, string, string, []string) {
details, err := vulnerability.Get(vulnID)
if err != nil {
log.Logger.Debug(err)
return vulnerability.SeverityUnknown, "", "", nil
} else if len(details) == 0 {
return vulnerability.SeverityUnknown, "", "", nil
}
return getSeverity(details), getTitle(details), getDescription(details), getReferences(details)
}

func getSeverity(details map[string]vulnerability.Vulnerability) vulnerability.Severity {
for _, source := range sources {
d, ok := details[source]
if !ok {
continue
}
if d.CvssScore > 0 {
return scoreToSeverity(d.CvssScore)
} else if d.CvssScoreV3 > 0 {
return scoreToSeverity(d.CvssScoreV3)
} else if d.Severity != 0 {
return d.Severity
} else if d.SeverityV3 != 0 {
return d.SeverityV3
}
}
return vulnerability.SeverityUnknown
}

func getTitle(details map[string]vulnerability.Vulnerability) string {
for _, source := range sources {
d, ok := details[source]
if !ok {
continue
}
if d.Title != "" {
return d.Title
}
}
return ""
}

func getDescription(details map[string]vulnerability.Vulnerability) string {
for _, source := range sources {
d, ok := details[source]
if !ok {
continue
}
if d.Description != "" {
return d.Description
}
}
return ""
}

func getReferences(details map[string]vulnerability.Vulnerability) []string {
references := map[string]struct{}{}
for _, source := range sources {
d, ok := details[source]
if !ok {
continue
}
for _, ref := range d.References {
references[ref] = struct{}{}
}
}
var refs []string
for ref := range references {
refs = append(refs, ref)
}
return refs
}

func scoreToSeverity(score float64) vulnerability.Severity {
if score >= 9.0 {
return vulnerability.SeverityCritical
} else if score >= 7.0 {
return vulnerability.SeverityHigh
} else if score >= 4.0 {
return vulnerability.SeverityMedium
} else if score > 0.0 {
return vulnerability.SeverityLow
}
return vulnerability.SeverityUnknown
}
164 changes: 164 additions & 0 deletions pkg/vulnsrc/vulnerability/vulnerability.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package vulnerability

import (
"bufio"
"fmt"
"os"
"strings"

"github.com/knqyf263/trivy/pkg/utils"

"sort"

"github.com/knqyf263/trivy/pkg/log"
)

const (
trivyIgnore = ".trivyignore"
)

var (
sources = []string{Nvd, RedHat, Debian, DebianOVAL, Alpine,
RubySec, RustSec, PhpSecurityAdvisories, NodejsSecurityWg, PythonSafetyDB}
)

func FillAndFilter(vulns []DetectedVulnerability, severities []Severity, ignoreUnfixed bool) []DetectedVulnerability {
ignoredIDs := getIgnoredIDs()
var vulnerabilities []DetectedVulnerability
for _, vuln := range vulns {
sev, title, description, references := getDetail(vuln.VulnerabilityID)

// Filter vulnerabilities by severity
for _, s := range severities {
if s == sev {
vuln.Severity = fmt.Sprint(sev)
vuln.Title = title
vuln.Description = description
vuln.References = references

// Ignore unfixed vulnerabilities
if ignoreUnfixed && vuln.FixedVersion == "" {
continue
} else if utils.StringInSlice(vuln.VulnerabilityID, ignoredIDs) {
continue
}
vulnerabilities = append(vulnerabilities, vuln)
break
}
}
}
sort.Slice(vulnerabilities, func(i, j int) bool {
if vulnerabilities[i].PkgName != vulnerabilities[j].PkgName {
return vulnerabilities[i].PkgName < vulnerabilities[j].PkgName
}
return CompareSeverityString(vulnerabilities[j].Severity, vulnerabilities[i].Severity)
})
return vulnerabilities
}

func getIgnoredIDs() []string {
f, err := os.Open(trivyIgnore)
if err != nil {
// trivy must work even if no .trivyignore exist
return nil
}

var ignoredIDs []string
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "#") || line == "" {
continue
}
ignoredIDs = append(ignoredIDs, line)
}
return ignoredIDs
}

func getDetail(vulnID string) (Severity, string, string, []string) {
details, err := Get(vulnID)
if err != nil {
log.Logger.Debug(err)
return SeverityUnknown, "", "", nil
} else if len(details) == 0 {
return SeverityUnknown, "", "", nil
}
return getSeverity(details), getTitle(details), getDescription(details), getReferences(details)
}

func getSeverity(details map[string]Vulnerability) Severity {
for _, source := range sources {
d, ok := details[source]
if !ok {
continue
}
if d.CvssScore > 0 {
return scoreToSeverity(d.CvssScore)
} else if d.CvssScoreV3 > 0 {
return scoreToSeverity(d.CvssScoreV3)
} else if d.Severity != 0 {
return d.Severity
} else if d.SeverityV3 != 0 {
return d.SeverityV3
}
}
return SeverityUnknown
}

func getTitle(details map[string]Vulnerability) string {
for _, source := range sources {
d, ok := details[source]
if !ok {
continue
}
if d.Title != "" {
return d.Title
}
}
return ""
}

func getDescription(details map[string]Vulnerability) string {
for _, source := range sources {
d, ok := details[source]
if !ok {
continue
}
if d.Description != "" {
return d.Description
}
}
return ""
}

func getReferences(details map[string]Vulnerability) []string {
references := map[string]struct{}{}
for _, source := range sources {
d, ok := details[source]
if !ok {
continue
}
for _, ref := range d.References {
references[ref] = struct{}{}
}
}
var refs []string
for ref := range references {
refs = append(refs, ref)
}
return refs
}

func scoreToSeverity(score float64) Severity {
if score >= 9.0 {
return SeverityCritical
} else if score >= 7.0 {
return SeverityHigh
} else if score >= 4.0 {
return SeverityMedium
} else if score > 0.0 {
return SeverityLow
}
return SeverityUnknown
}

0 comments on commit 27d776c

Please sign in to comment.