-
Notifications
You must be signed in to change notification settings - Fork 1
/
vulnerability_matcher.go
153 lines (125 loc) · 4.38 KB
/
vulnerability_matcher.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package govulners
import (
"strings"
"github.com/nextlinux/govulners/govulners/govulnerserr"
"github.com/nextlinux/govulners/govulners/match"
"github.com/nextlinux/govulners/govulners/matcher"
"github.com/nextlinux/govulners/govulners/pkg"
"github.com/nextlinux/govulners/govulners/store"
"github.com/nextlinux/govulners/govulners/vulnerability"
"github.com/nextlinux/govulners/internal/log"
)
type VulnerabilityMatcher struct {
Store store.Store
Matchers []matcher.Matcher
IgnoreRules []match.IgnoreRule
FailSeverity *vulnerability.Severity
NormalizeByCVE bool
}
func DefaultVulnerabilityMatcher(store store.Store) *VulnerabilityMatcher {
return &VulnerabilityMatcher{
Store: store,
Matchers: matcher.NewDefaultMatchers(matcher.Config{}),
}
}
func (m *VulnerabilityMatcher) FailAtOrAboveSeverity(severity *vulnerability.Severity) *VulnerabilityMatcher {
m.FailSeverity = severity
return m
}
func (m *VulnerabilityMatcher) WithMatchers(matchers []matcher.Matcher) *VulnerabilityMatcher {
m.Matchers = matchers
return m
}
func (m *VulnerabilityMatcher) WithIgnoreRules(ignoreRules []match.IgnoreRule) *VulnerabilityMatcher {
m.IgnoreRules = ignoreRules
return m
}
func (m *VulnerabilityMatcher) FindMatches(pkgs []pkg.Package, context pkg.Context) (*match.Matches, []match.IgnoredMatch, error) {
var ignoredMatches []match.IgnoredMatch
matches := matcher.FindMatches(m.Store, context.Distro, m.Matchers, pkgs)
matches, ignoredMatches = m.applyIgnoreRules(matches)
if m.NormalizeByCVE {
normalizedMatches := match.NewMatches()
for originalMatch := range matches.Enumerate() {
normalizedMatches.Add(m.normalizeByCVE(originalMatch))
}
// we apply the ignore rules again in case any of the transformations done during normalization
// regresses the results (relative to the already applied ignore rules). Why do we additionally apply
// the ignore rules before normalizing? In case the user has a rule that ignores a non-normalized
// vulnerability ID, we wantMatches to ensure that the rule is honored.
matches, ignoredMatches = m.applyIgnoreRules(normalizedMatches)
}
var err error
if m.FailSeverity != nil && HasSeverityAtOrAbove(m.Store, *m.FailSeverity, matches) {
err = govulnerserr.ErrAboveSeverityThreshold
}
return &matches, ignoredMatches, err
}
func (m *VulnerabilityMatcher) applyIgnoreRules(matches match.Matches) (match.Matches, []match.IgnoredMatch) {
var ignoredMatches []match.IgnoredMatch
if len(m.IgnoreRules) == 0 {
return matches, ignoredMatches
}
matches, ignoredMatches = match.ApplyIgnoreRules(matches, m.IgnoreRules)
if count := len(ignoredMatches); count > 0 {
log.Infof("ignoring %d matches due to user-provided ignore rules", count)
}
return matches, ignoredMatches
}
func (m *VulnerabilityMatcher) normalizeByCVE(match match.Match) match.Match {
if isCVE(match.Vulnerability.ID) {
return match
}
var effectiveCVERecordRefs []vulnerability.Reference
for _, ref := range match.Vulnerability.RelatedVulnerabilities {
if isCVE(ref.ID) {
effectiveCVERecordRefs = append(effectiveCVERecordRefs, ref)
break
}
}
switch len(effectiveCVERecordRefs) {
case 0:
// TODO: trace logging
return match
case 1:
break
default:
// TODO: trace logging
return match
}
ref := effectiveCVERecordRefs[0]
upstreamMetadata, err := m.Store.GetMetadata(ref.ID, ref.Namespace)
if err != nil {
log.Warnf("unable to fetch effective CVE metadata for id=%q namespace=%q : %v", ref.ID, ref.Namespace, err)
return match
}
if upstreamMetadata == nil {
return match
}
originalRef := vulnerability.Reference{
ID: match.Vulnerability.ID,
Namespace: match.Vulnerability.Namespace,
}
match.Vulnerability.ID = upstreamMetadata.ID
match.Vulnerability.Namespace = upstreamMetadata.Namespace
match.Vulnerability.RelatedVulnerabilities = []vulnerability.Reference{originalRef}
return match
}
func isCVE(id string) bool {
return strings.HasPrefix(strings.ToLower(id), "cve-")
}
func HasSeverityAtOrAbove(store vulnerability.MetadataProvider, severity vulnerability.Severity, matches match.Matches) bool {
if severity == vulnerability.UnknownSeverity {
return false
}
for m := range matches.Enumerate() {
metadata, err := store.GetMetadata(m.Vulnerability.ID, m.Vulnerability.Namespace)
if err != nil {
continue
}
if vulnerability.ParseSeverity(metadata.Severity) >= severity {
return true
}
}
return false
}