Skip to content
This repository was archived by the owner on Nov 26, 2024. It is now read-only.

Commit cdafd05

Browse files
authored
Added cache logging anf timeout configuration (#10)
New features: - Added -cache command line option to print cache information - Added timeout configuration Timeout and Expiration configuration entries now have units. Thus: - To set one second timeout, you would write 1s - To set expiration of two hours and thirty minutes, you would write 2h30m
1 parent 7b80745 commit cdafd05

14 files changed

Lines changed: 447 additions & 168 deletions

README.md

Lines changed: 63 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ This tool parses Go binary dependencies and calls [NVD database](https://nvd.nis
1111
- [Memcachier](#memcachier)
1212
- [Memcached](#memcached)
1313
- [File](#file)
14-
5. [Versions](#versions)
15-
6. [How to Fix Vulnerabilities](#how-to-fix-vulnerabilities)
16-
7. [Information about vulnerabilities](#information-about-vulnerabilities)
17-
8. [How Gobinsec works](#how-gobinsec-works)
18-
9. [License](#license)
14+
5. [Timeout and Expiration](#timeout-and-expiration)
15+
6. [Versions](#versions)
16+
7. [How to Fix Vulnerabilities](#how-to-fix-vulnerabilities)
17+
8. [Information about vulnerabilities](#information-about-vulnerabilities)
18+
9. [How Gobinsec works](#how-gobinsec-works)
19+
10. [License](#license)
1920

2021
## Installation
2122

@@ -68,6 +69,12 @@ Exit code is *1* if exposed vulnerabilities were found, *2* if there was an erro
6869
6970
You can pass *-verbose* option on command line to print vulnerability report, even if binary is not vulnerable and for all vulnerabilities, even if they are ignored or not exposed.
7071
72+
To print cache information, pass *-cache* on command line. This will print dependencies along with following symbols:
73+
74+
- **>>>** vulnerabilities sent in cache for given dependency
75+
- **<<<** vulnerabilities retrieved from cache for given dependency
76+
- **!!!** vulnerabilities missed in cache for given dependency
77+
7178
You can set *-strict* flag on command line so that vulnerabilities without version are considered matching dependency version. In this case, you should check vulnerability manually and disable it in configuration file if necessary.
7279
7380
You can pass configuration file with *-config config.yml*, see configuration section below.
@@ -86,16 +93,18 @@ Configuration file is in YAML format as follows:
8693
api-key: "28c6112c-a7bc-4a4e-9b14-75be6da02211"
8794
strict: false
8895
memcachier:
89-
address: mcx.cy.eu-central-1.ec2.memcachier.com:11211
90-
expiration: 86400
91-
username: foo
92-
password: bar
96+
address: "mcx.cy.eu-central-1.ec2.memcachier.com:11211"
97+
username: "username"
98+
password: "password"
99+
timeout: "1s"
100+
expiration: "24h"
93101
memcached:
94-
address: 127.0.0.1:11211
95-
expiration: 86400
102+
address: "127.0.0.1:11211"
103+
timeout: "1s"
104+
expiration: "24h"
96105
file:
97-
name: ~/.gobinsec-cache.yml
98-
expiration: 86400
106+
name: "~/.gobinsec-cache.yml"
107+
expiration: "24h"
99108
ignore:
100109
- "CVE-2020-14040"
101110
```
@@ -104,9 +113,9 @@ Configuration fields are the following:
104113

105114
- **api-key**: this is your NVD API key
106115
- **strict**: tells if we should consider vulnerability matches without version as matching dependency
107-
- **memcachier** is the configuration for *memcachier*, with **address**, **expiration** (time in seconds), **username** and **password**
108-
- **memcached** is the configuration for *memcached*, with **address** and **expiration** time in seconds
109-
- **file** is the configuration for *file* cache, with **name** and **expiration** time in seconds
116+
- **memcachier** is the configuration for *memcachier*, see below
117+
- **memcached** is the configuration for *memcached*, see below
118+
- **file** is the configuration for *file* cache, see below
110119
- **ignore**: a list of CVE vulnerabilities to ignore
111120

112121
You can also set NVD API Key in your environment with variable *NVD_API_KEY*. This key may be overwritten with value in configuration file. Your API key must be set in environment to be able to run integration tests (with target *integ*).
@@ -124,51 +133,84 @@ A cache is built with *Memcachier* if following section is found in configuratio
124133
```yaml
125134
memcachier:
126135
address: ...
127-
expiration: ...
128136
username: ...
129137
password: ...
138+
timeout: ...
139+
expiration: ...
130140
```
131141
132142
Else, il will look for following environment variables:
133143
134144
```
135145
MEMCACHIER_ADDRESS
136-
MEMCACHIER_EXPIRATION
137146
MEMCACHIER_USERNAME
138147
MEMCACHIER_PASSWORD
148+
MEMCACHIER_TIMEOUT
149+
MEMCACHIER_EXPIRATION
139150
```
140151

141152
[Memcachier](https://www.memcachier.com) is an online cache provider with free tiers.
142153

154+
*Timeout* and *Expiration* configuration entries are optional and their default values are *1s* and *24h*.
155+
143156
### Memcached
144157

145158
A cache is built with *Memcached* if following section is found in configuration file:
146159

147160
```yaml
148161
memcached:
149162
address: ...
163+
timeout: ...
150164
expiration: ...
151165
```
152166
153167
Else it will look for following environment variables:
154168
155169
```
156170
MEMCACHED_ADDRESS
171+
MEMCACHED_TIMEOUT
157172
MEMCACHED_EXPIRATION
158173
```
159174

175+
*Timeout* and *Expiration* configuration entries are optional and their default values are *1s* and *24h*.
176+
160177
A sample [docker-compose.yml](https://github.com/intercloud/gobinsec/blob/main/docker-compose.yml) file to start a *memcached* instance is provided in this project.
161178

162179
### File
163180

164-
If none of preceding configuration is found in configuration and none of related environment variables, *Gobinsec* will use *YAML* file for caching. By default, database file is stored in *~/.gobinsec-cache.yml* and cache duration is of one day (or *86400* seconds). You can overwrite these default values with following configuration section:
181+
A file cache is used is none of preceding options is configured. By default, database file in YAML format is stored in *~/.gobinsec-cache.yml* and cache duration is of one day (or *24h*). You can overwrite these default values with following configuration section:
165182

166183
```yaml
167184
file:
168-
name: "/path/to/file.yml"
169-
expiration: 86400
185+
name: "~/.gobinsec-cache.yml"
186+
expiration: "24h"
187+
```
188+
189+
Or with these environment variables:
190+
191+
```
192+
FILECACHE_FILE
193+
FILECACHE_EXPIRATION
170194
```
171195

196+
*Expiration* configuration entry is optional and its default value is *24h*.
197+
198+
## Timeout and Expiration
199+
200+
These configuration keys are durations with a time unit. Possible units are:
201+
202+
- **ns** for nanosecond
203+
- **us** or **µs** for microsecond
204+
- **ms** for millisecond
205+
- **s** for second
206+
- **m** for minute
207+
- **h** for hour
208+
209+
Thus, you could write, for instance:
210+
211+
- *1s* to set timeout to *1* second
212+
- *2h30m* to set expiration to *2* hours and *30* minutes
213+
172214
## Versions
173215

174216
Dependencies and vulnerabilities have versions. There are three types of them:

gobinsec/binary.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func LoadVulnerabilities(dependencies chan *Dependency, wg *sync.WaitGroup) {
7979
select {
8080
case dependency := <-dependencies:
8181
if err := dependency.LoadVulnerabilities(); err != nil {
82-
fmt.Printf("ERROR loading vulnerability: %v\n", err)
82+
fmt.Fprintf(os.Stderr, "ERROR loading vulnerability: %v\n", err)
8383
os.Exit(1)
8484
}
8585
wg.Done()
@@ -91,17 +91,17 @@ func LoadVulnerabilities(dependencies chan *Dependency, wg *sync.WaitGroup) {
9191

9292
// Report prints a report on terminal
9393
// nolint:gocyclo // this is life
94-
func (b *Binary) Report(verbose bool) {
94+
func (b *Binary) Report() {
9595
fmt.Printf("%s: ", filepath.Base(b.Path))
9696
if b.Vulnerable {
9797
ColorRed.Println("VULNERABLE")
9898
} else {
9999
ColorGreen.Println("OK")
100100
}
101-
if len(b.Dependencies) > 0 && (b.Vulnerable || verbose) {
101+
if len(b.Dependencies) > 0 && (b.Vulnerable || config.Verbose) {
102102
fmt.Println("dependencies:")
103103
for _, dependency := range b.Dependencies {
104-
if !dependency.Vulnerable && !verbose {
104+
if !dependency.Vulnerable && !config.Verbose {
105105
continue
106106
}
107107
fmt.Printf("- name: '%s'\n", dependency.Name)
@@ -110,7 +110,7 @@ func (b *Binary) Report(verbose bool) {
110110
if len(dependency.Vulnerabilities) > 0 {
111111
fmt.Println(" vulnerabilities:")
112112
for _, vulnerability := range dependency.Vulnerabilities {
113-
if !vulnerability.Exposed && !verbose {
113+
if !vulnerability.Exposed && !config.Verbose {
114114
continue
115115
}
116116
fmt.Printf(" - id: '%s'\n", vulnerability.ID)

gobinsec/cache-file.go

Lines changed: 75 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,23 @@
11
package gobinsec
22

33
import (
4+
"fmt"
45
"os"
5-
"path/filepath"
6-
"strings"
76
"sync"
87
"time"
98

109
"gopkg.in/yaml.v3"
1110
)
1211

12+
const (
13+
FileCacheDefaultFile = "~/.gobinsec-cache.yml"
14+
FileCacheDefaultExpiration = 24 * time.Hour
15+
)
16+
1317
// FileConfig is the configuration for file cache
1418
type FileConfig struct {
15-
File string `yaml:"name"`
16-
Expiration int32 `yaml:"expiration"`
17-
}
18-
19-
// NewFileConfig builds configuration for file cache
20-
func NewFileConfig(config *FileConfig) *FileConfig {
21-
if config == nil {
22-
config = &FileConfig{}
23-
}
24-
if config.File == "" {
25-
config.File = "~/.gobinsec-cache.yml"
26-
}
27-
if strings.HasPrefix(config.File, "~/") {
28-
home, err := os.UserHomeDir()
29-
if err != nil {
30-
return nil
31-
}
32-
config.File = filepath.Join(home, config.File[2:])
33-
}
34-
if config.Expiration == 0 {
35-
config.Expiration = 86400
36-
}
37-
return config
19+
File string `yaml:"name"`
20+
Expiration time.Duration `yaml:"expiration"`
3821
}
3922

4023
// DependencyCache is an entry of dependency cache
@@ -46,45 +29,75 @@ type DependencyCache struct {
4629
// FileCache is the cache instance
4730
type FileCache struct {
4831
File string
49-
Expiration int32
32+
Expiration time.Duration
5033
Cache map[string]DependencyCache
5134
}
5235

5336
// lock for memory cache
5437
var lock sync.RWMutex
5538

5639
// NewFileCache builds a cache using file
57-
func NewFileCache(config *FileConfig) Cache {
58-
cache := make(map[string]DependencyCache)
59-
file, err := os.ReadFile(config.File)
60-
if err == nil {
61-
if err := yaml.Unmarshal(file, cache); err != nil {
62-
cache = make(map[string]DependencyCache)
63-
}
40+
func NewFileCache(config *FileConfig) (Cache, error) {
41+
var err error
42+
config, err = NewFileConfig(config)
43+
if err != nil {
44+
return nil, err
6445
}
65-
fileCache := &FileCache{
46+
cache := make(map[string]DependencyCache)
47+
fileCache := FileCache{
6648
File: config.File,
6749
Expiration: config.Expiration,
6850
Cache: cache,
6951
}
70-
fileCache.CleanCache()
71-
return fileCache
52+
return &fileCache, nil
53+
}
54+
55+
// NewFileConfig builds configuration for file cache
56+
func NewFileConfig(config *FileConfig) (*FileConfig, error) {
57+
if config != nil {
58+
if config.File == "" {
59+
config.File = FileCacheDefaultFile
60+
}
61+
if config.Expiration == 0 {
62+
config.Expiration = FileCacheDefaultExpiration
63+
}
64+
} else {
65+
file := os.Getenv("FILECACHE_FILE")
66+
if file == "" {
67+
file = FileCacheDefaultFile
68+
}
69+
expiration, err := ParseDuration(os.Getenv("FILECACHE_EXPIRATION"), FileCacheDefaultExpiration)
70+
if err != nil {
71+
return nil, fmt.Errorf("parsing file cache expiration: %v", err)
72+
}
73+
config = &FileConfig{
74+
File: file,
75+
Expiration: expiration,
76+
}
77+
}
78+
config.File = ExpandHome(config.File)
79+
return config, nil
80+
}
81+
82+
// Name returns the name of this cache
83+
func (fc *FileCache) Name() string {
84+
return "File"
7285
}
7386

7487
// Get returns NVD response for given dependency
75-
func (fc *FileCache) Get(d *Dependency) []byte {
88+
func (fc *FileCache) Get(d *Dependency) ([]byte, error) {
7689
key := d.Key()
7790
lock.RLock()
7891
defer lock.RUnlock()
7992
dependency, ok := fc.Cache[key]
8093
if ok {
81-
return []byte(dependency.Vulnerabilities)
94+
return []byte(dependency.Vulnerabilities), nil
8295
}
83-
return nil
96+
return nil, nil
8497
}
8598

8699
// Set put NVD response for given dependency in cache
87-
func (fc *FileCache) Set(d *Dependency, v []byte) {
100+
func (fc *FileCache) Set(d *Dependency, v []byte) error {
88101
key := d.Key()
89102
lock.Lock()
90103
defer lock.Unlock()
@@ -94,25 +107,40 @@ func (fc *FileCache) Set(d *Dependency, v []byte) {
94107
Vulnerabilities: string(v),
95108
}
96109
fc.Cache[key] = cache
110+
return nil
97111
}
98112

99-
// Ping does nothing
113+
// Open and load file cache if exists
100114
func (fc *FileCache) Open() error {
115+
if FileExists(fc.File) {
116+
file, err := os.ReadFile(fc.File)
117+
if err != nil {
118+
return fmt.Errorf("reading file cache: %v", err)
119+
} else {
120+
if err := yaml.Unmarshal(file, fc.Cache); err != nil {
121+
return fmt.Errorf("unmarshaling file cache: %v", err)
122+
}
123+
}
124+
fc.CleanCache()
125+
}
101126
return nil
102127
}
103128

104-
// Clean saves file
105-
func (fc *FileCache) Close() {
129+
// Close saves cache in file
130+
func (fc *FileCache) Close() error {
106131
text, err := yaml.Marshal(fc.Cache)
107-
if err == nil {
108-
os.WriteFile(fc.File, text, 0644) // nolint:errcheck,gosec,gomnd // if error writing cache, do nothing
132+
if err != nil {
133+
return fmt.Errorf("marshaling file cache: %v", err)
109134
}
135+
if err := os.WriteFile(fc.File, text, 0644); err != nil { // nolint:gosec,gomnd // no confidential data in file
136+
return fmt.Errorf("saving file cache: %v", err)
137+
}
138+
return nil
110139
}
111140

112141
// CleanCache removes obsolete cache entries
113142
func (fc *FileCache) CleanCache() {
114-
duration := time.Duration(fc.Expiration) * time.Second
115-
limit := time.Now().UTC().Add(-duration).Format("2006-01-02T15:04:05")
143+
limit := time.Now().UTC().Add(-fc.Expiration).Format("2006-01-02T15:04:05")
116144
for name, cache := range fc.Cache {
117145
if cache.Date < limit {
118146
delete(fc.Cache, name)

0 commit comments

Comments
 (0)