-
Notifications
You must be signed in to change notification settings - Fork 194
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add ability to filter/ignore specific vulnerabilities (#778)
Solves #662 > By placing a `osv-scanner.toml` file in any parent directory of the file being > scanned will be used to configure scanning of that file. This can be overridden > by passing the `--config=/path/to/config` flag. > > Currently, there is only 1 option to configure: > ### Ignore vulnerabilities by ID > Vulnerabilities can be marked as ignored by putting the vuln ID in an array > under the `IgnoreVulnIds` key. > ``` > IgnoredVulnIds = [ > "GO-2022-0968", > "GO-2022-1059" > ] > ``` Co-authored-by: Andrew Pollock <[email protected]>
- Loading branch information
1 parent
ab01de1
commit ca38cad
Showing
14 changed files
with
417 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
[[IgnoredVulns]] | ||
id = "GO-2022-0968" | ||
# ignoreUntil = 2022-11-09 # Optional exception expiry date | ||
reason = "No ssh servers are connected to or hosted in Go lang" | ||
|
||
[[IgnoredVulns]] | ||
id = "GO-2022-1059" | ||
# ignoreUntil = 2022-11-09 # Optional exception expiry date | ||
reason = "No external http servers are written in Go lang." | ||
|
||
[[IgnoredVulns]] | ||
id = "GO-2022-0356" | ||
# ignoreUntil = 2022-11-09 # Optional exception expiry date | ||
reason = "No ssh servers" | ||
|
||
[[IgnoredVulns]] | ||
id = "GO-2022-0969" | ||
# ignoreUntil = 2022-11-09 # Optional exception expiry date | ||
reason = "No external http servers in Go lang." |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
[[IgnoredVulns]] | ||
id = "GO-2022-0968" | ||
# ignoreUntil = 2022-11-09 # Optional exception expiry date | ||
reason = "No ssh servers are connected to or hosted in Go lang" | ||
|
||
[[IgnoredVulns]] | ||
id = "GO-2022-1059" | ||
# ignoreUntil = 2022-11-09 # Optional exception expiry date | ||
reason = "No external http servers are written in Go lang." | ||
|
||
[[IgnoredVulns]] | ||
id = "GO-2022-0356" | ||
# ignoreUntil = 2022-11-09 # Optional exception expiry date | ||
reason = "No ssh servers" | ||
|
||
[[IgnoredVulns]] | ||
id = "GO-2022-0969" | ||
# ignoreUntil = 2022-11-09 # Optional exception expiry date | ||
reason = "No external http servers in Go lang." |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"os" | ||
"path/filepath" | ||
"time" | ||
|
||
"github.com/BurntSushi/toml" | ||
"golang.org/x/exp/slices" | ||
) | ||
|
||
type ConfigManager struct { | ||
// Override to replace all other configs | ||
overrideConfig *Config | ||
// Config to use if no config file is found alongside manifests | ||
defaultConfig Config | ||
// Cache to store loaded configs | ||
configMap map[string]Config | ||
} | ||
|
||
type Config struct { | ||
IgnoredVulns []IgnoreEntry | ||
LoadPath string | ||
} | ||
|
||
type IgnoreEntry struct { | ||
ID string `toml:"id"` | ||
IgnoreUntil time.Time `toml:"ignoreUntil"` | ||
Reason string `toml:"reason"` | ||
} | ||
|
||
func (c *Config) ShouldIgnore(vulnID string) (bool, IgnoreEntry) { | ||
index := slices.IndexFunc(c.IgnoredVulns, func(elem IgnoreEntry) bool { return elem.ID == vulnID }) | ||
if index == -1 { | ||
return false, IgnoreEntry{} | ||
} | ||
ignoredLine := c.IgnoredVulns[index] | ||
if ignoredLine.IgnoreUntil.IsZero() { | ||
// If IgnoreUntil is not set, should ignore. | ||
return true, ignoredLine | ||
} | ||
// Should ignore if IgnoreUntil is still after current time | ||
// Takes timezone offsets into account if it is specified. otherwise it's using local time | ||
return ignoredLine.IgnoreUntil.After(time.Now()), ignoredLine | ||
|
||
} | ||
|
||
// Sets the override config by reading the config file at configPath. | ||
// Will return an error if loading the config file fails | ||
func (c *ConfigManager) UseOverride(configPath string) error { | ||
config := Config{} | ||
_, err := toml.DecodeFile(configPath, &config) | ||
if err != nil { | ||
return err | ||
} | ||
config.LoadPath = configPath | ||
c.overrideConfig = &config | ||
return nil | ||
} | ||
|
||
// Attempts to get the config | ||
func (c *ConfigManager) Get(targetPath string) Config { | ||
if c.overrideConfig != nil { | ||
return *c.overrideConfig | ||
} | ||
|
||
configPath := normalizeConfigLoadPath(targetPath) | ||
config, alreadyExists := c.configMap[configPath] | ||
if alreadyExists { | ||
return config | ||
} | ||
|
||
config, configErr := tryLoadConfig(configPath) | ||
if configErr == nil { | ||
log.Printf("Loaded filter from: %s", config.LoadPath) | ||
} else { | ||
// If config doesn't exist, use the default config | ||
config = c.defaultConfig | ||
} | ||
c.configMap[configPath] = config | ||
|
||
return config | ||
} | ||
|
||
// Finds the containing folder of `target`, then appends osvScannerConfigName | ||
func normalizeConfigLoadPath(target string) string { | ||
stat, err := os.Stat(target) | ||
if err != nil { | ||
log.Fatalf("Failed to stat target: %s", err) | ||
} | ||
|
||
var containingFolder string | ||
if !stat.IsDir() { | ||
containingFolder = filepath.Dir(target) | ||
} else { | ||
containingFolder = target | ||
} | ||
configPath := filepath.Join(containingFolder, osvScannerConfigName) | ||
return configPath | ||
} | ||
|
||
// tryLoadConfig tries to load config in `target` (or it's containing directory) | ||
// `target` will be the key for the entry in configMap | ||
func tryLoadConfig(configPath string) (Config, error) { | ||
configFile, err := os.Open(configPath) | ||
var config Config | ||
if err == nil { // File exists, and we have permission to read | ||
_, err := toml.NewDecoder(configFile).Decode(&config) | ||
if err != nil { | ||
log.Fatalf("Failed to read config file: %s\n", err) | ||
} | ||
config.LoadPath = configPath | ||
return config, nil | ||
} | ||
|
||
return Config{}, fmt.Errorf("No config file found on this path: %s", configPath) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package main | ||
|
||
import ( | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
) | ||
|
||
type testStruct struct { | ||
targetPath string | ||
config Config | ||
configHasErr bool | ||
} | ||
|
||
func TestTryLoadConfig(t *testing.T) { | ||
|
||
expectedConfig := Config{ | ||
IgnoredVulns: []IgnoreEntry{ | ||
{ | ||
ID: "GO-2022-0968", | ||
}, | ||
{ | ||
ID: "GO-2022-1059", | ||
}, | ||
}, | ||
} | ||
testPaths := []testStruct{ | ||
{ | ||
targetPath: "../../testdata/testdatainner/innerFolder/test.yaml", | ||
config: expectedConfig, | ||
configHasErr: true, | ||
}, | ||
{ | ||
targetPath: "../../testdata/testdatainner/innerFolder/", | ||
config: Config{}, | ||
configHasErr: true, | ||
}, | ||
{ // Test no slash at the end | ||
targetPath: "../../testdata/testdatainner/innerFolder", | ||
config: Config{}, | ||
configHasErr: true, | ||
}, | ||
{ | ||
targetPath: "../../testdata/testdatainner/", | ||
config: expectedConfig, | ||
configHasErr: false, | ||
}, | ||
{ | ||
targetPath: "../../testdata/testdatainner/some-manifest.yaml", | ||
config: expectedConfig, | ||
configHasErr: false, | ||
}, | ||
} | ||
|
||
for _, testData := range testPaths { | ||
absPath, err := filepath.Abs(testData.targetPath) | ||
if err != nil { | ||
t.Errorf("%s", err) | ||
} | ||
configPath := normalizeConfigLoadPath(absPath) | ||
config, configErr := tryLoadConfig(configPath) | ||
cmp.Equal(config, testData.config) | ||
if testData.configHasErr { | ||
cmp.Equal(configErr, nil) | ||
} | ||
} | ||
} |
Oops, something went wrong.