Skip to content

Commit

Permalink
Add ability to filter/ignore specific vulnerabilities (#778)
Browse files Browse the repository at this point in the history
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 <andrewpollock@users.noreply.github.com>
  • Loading branch information
another-rex and andrewpollock committed Nov 16, 2022
1 parent ab01de1 commit ca38cad
Show file tree
Hide file tree
Showing 14 changed files with 417 additions and 37 deletions.
19 changes: 19 additions & 0 deletions docker/indexer/osv-scanner.toml
@@ -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."
19 changes: 19 additions & 0 deletions osv-scanner.toml
@@ -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."
28 changes: 26 additions & 2 deletions tools/osv-scanner/README.md
Expand Up @@ -71,7 +71,7 @@ This tool will walk through a list of directories to find:

and make requests to OSV to determine affected vulnerabilities.

You can have it recursively walk through subdirectories with the `--recursive` flag.
You can have it recursively walk through subdirectories with the `--recursive` / `-r` flag.

Searching for git commit hash is intended to work with projects that use
git submodules or a similar mechanism where dependencies are checked out
Expand All @@ -80,8 +80,32 @@ as real git repositories.
### Example

```bash
$ go run ./cmd/osv-scanner /path/to/your/dir
$ go run ./cmd/osv-scanner -r /path/to/your/dir
```

## Configure `osv-scanner`

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 ID an entry
under the `IgnoreVulns` key, along with optional reason and expiry date.

#### Example
```
[[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"
id = "GO-2022-1059"
# ignoreUntil = 2022-11-09 # Optional exception expiry date
reason = "No external http servers are written in Go lang."
```

## JSON output
By default osv-scanner outputs a human readable table. To have osv-scanner output JSON instead, pass the `--json` flag when calling osv-scanner.

Expand Down
119 changes: 119 additions & 0 deletions tools/osv-scanner/cmd/osv-scanner/config.go
@@ -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)
}
68 changes: 68 additions & 0 deletions tools/osv-scanner/cmd/osv-scanner/config_test.go
@@ -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)
}
}
}

0 comments on commit ca38cad

Please sign in to comment.