Skip to content

Commit 6d0e29e

Browse files
authored
implement filtering by packages through the config (#944)
I'd like feedback on the config yaml schema, the filter message and it's behaviour if the version is empty (it filters any version of that package). This is in response to #814
1 parent 02a802d commit 6d0e29e

File tree

7 files changed

+1003
-372
lines changed

7 files changed

+1003
-372
lines changed
+18-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,25 @@
11
[[IgnoredVulns]]
22
id = "GO-2022-0968"
3-
# ignore_until = 2022-11-09
3+
# ignoreUntil = 2022-11-09
44
# reason = "" # Optional reason
55

66
[[IgnoredVulns]]
77
id = "GO-2022-1059"
8-
# ignore_until = 2022-11-09 # Optional exception expiry date
8+
# ignoreUntil = 2022-11-09 # Optional exception expiry date
99
# reason = "" # Optional reason
10+
11+
[[PackageOverrides]]
12+
name = "lib"
13+
version = "1.0.0"
14+
ecosystem = "Go"
15+
ignore = true
16+
# effectiveUntil = 2022-11-09 # Optional exception expiry date
17+
reason = "abc"
18+
19+
[[PackageOverrides]]
20+
name = "my-pkg"
21+
version = "1.0.0"
22+
ecosystem = "Go"
23+
ignore = true
24+
reason = "abc"
25+
license.override = ["MIT", "0BSD"]

pkg/config/config.go

+56-7
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ type ConfigManager struct {
2323
}
2424

2525
type Config struct {
26-
IgnoredVulns []IgnoreEntry `toml:"IgnoredVulns"`
27-
LoadPath string `toml:"LoadPath"`
28-
GoVersionOverride string `toml:"GoVersionOverride"`
26+
IgnoredVulns []IgnoreEntry `toml:"IgnoredVulns"`
27+
PackageOverrides []PackageOverrideEntry `toml:"PackageOverrides"`
28+
LoadPath string `toml:"LoadPath"`
29+
GoVersionOverride string `toml:"GoVersionOverride"`
2930
}
3031

3132
type IgnoreEntry struct {
@@ -34,19 +35,67 @@ type IgnoreEntry struct {
3435
Reason string `toml:"reason"`
3536
}
3637

38+
type PackageOverrideEntry struct {
39+
Name string `toml:"name"`
40+
// If the version is empty, the entry applies to all versions.
41+
Version string `toml:"version"`
42+
Ecosystem string `toml:"ecosystem"`
43+
Ignore bool `toml:"ignore"`
44+
License License `toml:"license"`
45+
EffectiveUntil time.Time `toml:"effectiveUntil"`
46+
Reason string `toml:"reason"`
47+
}
48+
49+
type License struct {
50+
Override []string `toml:"override"`
51+
}
52+
3753
func (c *Config) ShouldIgnore(vulnID string) (bool, IgnoreEntry) {
38-
index := slices.IndexFunc(c.IgnoredVulns, func(elem IgnoreEntry) bool { return elem.ID == vulnID })
54+
index := slices.IndexFunc(c.IgnoredVulns, func(e IgnoreEntry) bool { return e.ID == vulnID })
3955
if index == -1 {
4056
return false, IgnoreEntry{}
4157
}
4258
ignoredLine := c.IgnoredVulns[index]
43-
if ignoredLine.IgnoreUntil.IsZero() {
59+
60+
return shouldIgnoreTimestamp(ignoredLine.IgnoreUntil), ignoredLine
61+
}
62+
63+
func (c *Config) filterPackageVersionEntries(name string, version string, ecosystem string, condition func(PackageOverrideEntry) bool) (bool, PackageOverrideEntry) {
64+
index := slices.IndexFunc(c.PackageOverrides, func(e PackageOverrideEntry) bool {
65+
if ecosystem != e.Ecosystem || name != e.Name {
66+
return false
67+
}
68+
69+
return (version == e.Version || e.Version == "") && condition(e)
70+
})
71+
if index == -1 {
72+
return false, PackageOverrideEntry{}
73+
}
74+
ignoredLine := c.PackageOverrides[index]
75+
76+
return shouldIgnoreTimestamp(ignoredLine.EffectiveUntil), ignoredLine
77+
}
78+
79+
func (c *Config) ShouldIgnorePackageVersion(name, version, ecosystem string) (bool, PackageOverrideEntry) {
80+
return c.filterPackageVersionEntries(name, version, ecosystem, func(e PackageOverrideEntry) bool {
81+
return e.Ignore
82+
})
83+
}
84+
85+
func (c *Config) ShouldOverridePackageVersionLicense(name, version, ecosystem string) (bool, PackageOverrideEntry) {
86+
return c.filterPackageVersionEntries(name, version, ecosystem, func(e PackageOverrideEntry) bool {
87+
return len(e.License.Override) > 0
88+
})
89+
}
90+
91+
func shouldIgnoreTimestamp(ignoreUntil time.Time) bool {
92+
if ignoreUntil.IsZero() {
4493
// If IgnoreUntil is not set, should ignore.
45-
return true, ignoredLine
94+
return true
4695
}
4796
// Should ignore if IgnoreUntil is still after current time
4897
// Takes timezone offsets into account if it is specified. otherwise it's using local time
49-
return ignoredLine.IgnoreUntil.After(time.Now()), ignoredLine
98+
return ignoreUntil.After(time.Now())
5099
}
51100

52101
// Sets the override config by reading the config file at configPath.

pkg/config/config_internal_test.go

+257
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,25 @@ func TestTryLoadConfig(t *testing.T) {
2727
ID: "GO-2022-1059",
2828
},
2929
},
30+
PackageOverrides: []PackageOverrideEntry{
31+
{
32+
Name: "lib",
33+
Version: "1.0.0",
34+
Ecosystem: "Go",
35+
Ignore: true,
36+
Reason: "abc",
37+
},
38+
{
39+
Name: "my-pkg",
40+
Version: "1.0.0",
41+
Ecosystem: "Go",
42+
Reason: "abc",
43+
Ignore: true,
44+
License: License{
45+
Override: []string{"MIT", "0BSD"},
46+
},
47+
},
48+
},
3049
}
3150
testPaths := []testStruct{
3251
{
@@ -69,6 +88,9 @@ func TestTryLoadConfig(t *testing.T) {
6988
if !cmp.Equal(config.IgnoredVulns, testData.config.IgnoredVulns) {
7089
t.Errorf("Configs not equal: %+v != %+v", config, testData.config)
7190
}
91+
if !cmp.Equal(config.PackageOverrides, testData.config.PackageOverrides) {
92+
t.Errorf("Configs not equal: %+v != %+v", config, testData.config)
93+
}
7294
if testData.configHasErr {
7395
if configErr == nil {
7496
t.Error("Config error not returned")
@@ -191,3 +213,238 @@ func TestConfig_ShouldIgnore(t *testing.T) {
191213
})
192214
}
193215
}
216+
217+
func TestConfig_ShouldIgnorePackageVersion(t *testing.T) {
218+
t.Parallel()
219+
220+
type args struct {
221+
name string
222+
version string
223+
ecosystem string
224+
}
225+
tests := []struct {
226+
name string
227+
config Config
228+
args args
229+
wantOk bool
230+
wantEntry PackageOverrideEntry
231+
}{
232+
{
233+
name: "Version-level entry exists",
234+
config: Config{
235+
PackageOverrides: []PackageOverrideEntry{
236+
{
237+
Name: "lib1",
238+
Version: "1.0.0",
239+
Ecosystem: "Go",
240+
Ignore: true,
241+
EffectiveUntil: time.Time{},
242+
Reason: "abc",
243+
},
244+
},
245+
},
246+
args: args{
247+
name: "lib1",
248+
version: "1.0.0",
249+
ecosystem: "Go",
250+
},
251+
wantOk: true,
252+
wantEntry: PackageOverrideEntry{
253+
Name: "lib1",
254+
Version: "1.0.0",
255+
Ecosystem: "Go",
256+
Ignore: true,
257+
EffectiveUntil: time.Time{},
258+
Reason: "abc",
259+
},
260+
},
261+
{
262+
name: "Package-level entry exists",
263+
config: Config{
264+
PackageOverrides: []PackageOverrideEntry{
265+
{
266+
Name: "lib1",
267+
Ecosystem: "Go",
268+
Ignore: true,
269+
EffectiveUntil: time.Time{},
270+
Reason: "abc",
271+
},
272+
},
273+
},
274+
args: args{
275+
name: "lib1",
276+
version: "1.0.0",
277+
ecosystem: "Go",
278+
},
279+
wantOk: true,
280+
wantEntry: PackageOverrideEntry{
281+
Name: "lib1",
282+
Ecosystem: "Go",
283+
Ignore: true,
284+
EffectiveUntil: time.Time{},
285+
Reason: "abc",
286+
},
287+
},
288+
{
289+
name: "Entry doesn't exist",
290+
config: Config{
291+
PackageOverrides: []PackageOverrideEntry{
292+
{
293+
Name: "lib1",
294+
Version: "2.0.0",
295+
Ecosystem: "Go",
296+
Ignore: false,
297+
EffectiveUntil: time.Time{},
298+
Reason: "abc",
299+
},
300+
{
301+
Name: "lib2",
302+
Version: "2.0.0",
303+
Ignore: true,
304+
Ecosystem: "Go",
305+
EffectiveUntil: time.Time{},
306+
Reason: "abc",
307+
},
308+
},
309+
},
310+
args: args{
311+
name: "lib1",
312+
version: "2.0.0",
313+
ecosystem: "Go",
314+
},
315+
wantOk: false,
316+
wantEntry: PackageOverrideEntry{},
317+
},
318+
}
319+
320+
for _, tt := range tests {
321+
tt := tt
322+
t.Run(tt.name, func(t *testing.T) {
323+
t.Parallel()
324+
325+
gotOk, gotEntry := tt.config.ShouldIgnorePackageVersion(tt.args.name, tt.args.version, tt.args.ecosystem)
326+
if gotOk != tt.wantOk {
327+
t.Errorf("ShouldIgnorePackageVersion() gotOk = %v, wantOk %v", gotOk, tt.wantOk)
328+
}
329+
if !reflect.DeepEqual(gotEntry, tt.wantEntry) {
330+
t.Errorf("ShouldIgnorePackageVersion() gotEntry = %v, wantEntry %v", gotEntry, tt.wantEntry)
331+
}
332+
})
333+
}
334+
}
335+
336+
func TestConfig_ShouldOverridePackageVersionLicense(t *testing.T) {
337+
t.Parallel()
338+
339+
type args struct {
340+
name string
341+
version string
342+
ecosystem string
343+
}
344+
tests := []struct {
345+
name string
346+
config Config
347+
args args
348+
wantOk bool
349+
wantEntry PackageOverrideEntry
350+
}{
351+
{
352+
name: "Exact version entry exists",
353+
config: Config{
354+
PackageOverrides: []PackageOverrideEntry{
355+
{
356+
Name: "lib1",
357+
Version: "1.0.0",
358+
Ecosystem: "Go",
359+
License: License{
360+
Override: []string{"mit"},
361+
},
362+
Reason: "abc",
363+
},
364+
},
365+
},
366+
args: args{
367+
name: "lib1",
368+
version: "1.0.0",
369+
ecosystem: "Go",
370+
},
371+
wantOk: true,
372+
wantEntry: PackageOverrideEntry{
373+
Name: "lib1",
374+
Version: "1.0.0",
375+
Ecosystem: "Go",
376+
License: License{
377+
Override: []string{"mit"},
378+
},
379+
Reason: "abc",
380+
},
381+
},
382+
{
383+
name: "Version entry doesn't exist",
384+
config: Config{
385+
PackageOverrides: []PackageOverrideEntry{
386+
{
387+
Name: "lib1",
388+
Version: "1.0.0",
389+
Ecosystem: "Go",
390+
License: License{
391+
Override: []string{"mit"},
392+
},
393+
Reason: "abc",
394+
},
395+
},
396+
},
397+
args: args{
398+
name: "lib1",
399+
version: "1.0.1",
400+
ecosystem: "Go",
401+
},
402+
wantOk: false,
403+
wantEntry: PackageOverrideEntry{},
404+
},
405+
{
406+
name: "Name matches",
407+
config: Config{
408+
PackageOverrides: []PackageOverrideEntry{
409+
{
410+
Name: "lib1",
411+
Ecosystem: "Go",
412+
License: License{
413+
Override: []string{"mit"},
414+
},
415+
Reason: "abc",
416+
},
417+
},
418+
},
419+
args: args{
420+
name: "lib1",
421+
version: "1.0.1",
422+
ecosystem: "Go",
423+
},
424+
wantOk: true,
425+
wantEntry: PackageOverrideEntry{
426+
Name: "lib1",
427+
Ecosystem: "Go",
428+
License: License{
429+
Override: []string{"mit"},
430+
},
431+
Reason: "abc",
432+
},
433+
},
434+
}
435+
436+
for _, tt := range tests {
437+
tt := tt
438+
t.Run(tt.name, func(t *testing.T) {
439+
t.Parallel()
440+
441+
gotOk, gotEntry := tt.config.ShouldOverridePackageVersionLicense(tt.args.name, tt.args.version, tt.args.ecosystem)
442+
if gotOk != tt.wantOk {
443+
t.Errorf("ShouldOverridePackageVersionLicense() gotOk = %v, wantOk %v", gotOk, tt.wantOk)
444+
}
445+
if !reflect.DeepEqual(gotEntry, tt.wantEntry) {
446+
t.Errorf("ShouldOverridePackageVersionLicense() gotEntry = %v, wantEntry %v", gotEntry, tt.wantEntry)
447+
}
448+
})
449+
}
450+
}

0 commit comments

Comments
 (0)