Skip to content

Commit abe7a41

Browse files
rolandshoemakerFiloSottile
authored andcommitted
all: initial commit
0 parents  commit abe7a41

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+3296
-0
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
This repository contains a handful of prototypes for the Go vulnerability database,
2+
as well as a initial set of vulnerability reports. Some of these packages can probably
3+
be coalesced, but for now are easier to work on in a more segmented fashion.
4+
5+
* `reports` contains TOML security reports, the format is described in `format.md`
6+
* `report` provides a package for parsing and linting TOML reports
7+
* `osv` provides a package for generating OSV-style JSON vulnerability entries from a `report.Report`
8+
* `client` contains a client for accesing HTTP/fs based vulnerability databases, as well as a minimal caching implementation
9+
* `cmd/gendb` provides a tool for converting TOML reports into JSON database
10+
* `cmd/genhtml` provides a tool for converting TOML reports into a HTML website
11+
* `cmd/linter` provides a tool for linting individual reports
12+
* `cmd/report2cve` provides a tool for converting TOML reports into JSON CVEs

client/cache.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package client
2+
3+
import (
4+
"encoding/json"
5+
"go/build"
6+
"os"
7+
"path/filepath"
8+
"time"
9+
10+
"golang.org/x/vulndb/osv"
11+
)
12+
13+
// NOTE: this cache implementation should be internal to the go tooling
14+
// (i.e. cmd/go/internal/something) so that the vulndb cache is owned
15+
// by the go command. Also it is currently NOT CONCURRENCY SAFE since
16+
// it does not implement file locking. When ported to the stdlib it
17+
// should use cmd/go/internal/lockedfile.
18+
19+
// The cahce uses a single JSON index file for each vulnerability database
20+
// which contains the map from packages to the time the last
21+
// vulnerability for that package was added/modified and the time that
22+
// the index was retrieved from the vulnerability database. The JSON
23+
// format is as follows:
24+
//
25+
// $GOPATH/pkg/mod/cache/download/vulndb/{db hostname}/indexes/index.json
26+
// {
27+
// Retrieved time.Time
28+
// Index map[string]time.Time
29+
// }
30+
//
31+
// Each package also has a JSON file which contains the array of vulnerability
32+
// entries for the package. The JSON format is as follows:
33+
//
34+
// $GOPATH/pkg/mod/cache/download/vulndb/{db hostname}/{import path}/vulns.json
35+
// []*osv.Entry
36+
37+
type Cache interface {
38+
ReadIndex(string) (map[string]time.Time, time.Time, error)
39+
WriteIndex(string, map[string]time.Time, time.Time) error
40+
ReadEntries(string, string) ([]*osv.Entry, error)
41+
WriteEntries(string, string, []*osv.Entry) error
42+
}
43+
44+
type fsCache struct{}
45+
46+
// should be cfg.GOMODCACHE when doing this inside the cmd/go/internal
47+
var cacheRoot = filepath.Join(build.Default.GOPATH, "/pkg/mod/cache/downlaod/vulndb")
48+
49+
type cachedIndex struct {
50+
Retrieved time.Time
51+
Index map[string]time.Time
52+
}
53+
54+
func (c *fsCache) ReadIndex(dbName string) (map[string]time.Time, time.Time, error) {
55+
b, err := os.ReadFile(filepath.Join(cacheRoot, dbName, "index.json"))
56+
if err != nil {
57+
if os.IsNotExist(err) {
58+
return nil, time.Time{}, nil
59+
}
60+
return nil, time.Time{}, err
61+
}
62+
var index cachedIndex
63+
if err := json.Unmarshal(b, &index); err != nil {
64+
return nil, time.Time{}, err
65+
}
66+
return index.Index, index.Retrieved, nil
67+
}
68+
69+
func (c *fsCache) WriteIndex(dbName string, index map[string]time.Time, retrieved time.Time) error {
70+
path := filepath.Join(cacheRoot, dbName)
71+
if err := os.MkdirAll(path, 0777); err != nil {
72+
return err
73+
}
74+
j, err := json.Marshal(cachedIndex{
75+
Index: index,
76+
Retrieved: retrieved,
77+
})
78+
if err != nil {
79+
return err
80+
}
81+
if err := os.WriteFile(filepath.Join(path, "index.json"), j, 0666); err != nil {
82+
return err
83+
}
84+
return nil
85+
}
86+
87+
func (c *fsCache) ReadEntries(dbName string, p string) ([]*osv.Entry, error) {
88+
b, err := os.ReadFile(filepath.Join(cacheRoot, dbName, p, "vulns.json"))
89+
if err != nil {
90+
if os.IsNotExist(err) {
91+
return nil, nil
92+
}
93+
return nil, err
94+
}
95+
var entries []*osv.Entry
96+
if err := json.Unmarshal(b, &entries); err != nil {
97+
return nil, err
98+
}
99+
return entries, nil
100+
}
101+
102+
func (c *fsCache) WriteEntries(dbName string, p string, entries []*osv.Entry) error {
103+
path := filepath.Join(cacheRoot, dbName, p)
104+
if err := os.MkdirAll(path, 0777); err != nil {
105+
return err
106+
}
107+
j, err := json.Marshal(entries)
108+
if err != nil {
109+
return err
110+
}
111+
if err := os.WriteFile(filepath.Join(path, "vulns.json"), j, 0666); err != nil {
112+
return err
113+
}
114+
return nil
115+
}

client/cache_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package client
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"reflect"
7+
"testing"
8+
"time"
9+
10+
"golang.org/x/vulndb/osv"
11+
)
12+
13+
func TestCache(t *testing.T) {
14+
originalRoot := cacheRoot
15+
defer func() { cacheRoot = originalRoot }()
16+
17+
tmp, err := os.MkdirTemp("", "vulndb-cache")
18+
if err != nil {
19+
t.Fatal(err)
20+
}
21+
defer os.RemoveAll(tmp)
22+
cacheRoot = tmp
23+
24+
cache := &fsCache{}
25+
dbName := "vulndb.golang.org"
26+
27+
_, _, err = cache.ReadIndex(dbName)
28+
if err != nil {
29+
t.Fatalf("ReadIndex failed for non-existent database: %v", err)
30+
}
31+
32+
if err = os.Mkdir(filepath.Join(tmp, dbName), 0777); err != nil {
33+
t.Fatalf("os.Mkdir failed: %v", err)
34+
}
35+
_, _, err = cache.ReadIndex(dbName)
36+
if err != nil {
37+
t.Fatalf("ReadIndex failed for database without cached index: %v", err)
38+
}
39+
40+
now := time.Now()
41+
expectedIdx := map[string]time.Time{
42+
"a.vuln.example.com": time.Time{}.Add(time.Hour),
43+
"b.vuln.example.com": time.Time{}.Add(time.Hour * 2),
44+
"c.vuln.example.com": time.Time{}.Add(time.Hour * 3),
45+
}
46+
if err = cache.WriteIndex(dbName, expectedIdx, now); err != nil {
47+
t.Fatalf("WriteIndex failed to write index: %v", err)
48+
}
49+
50+
idx, retrieved, err := cache.ReadIndex(dbName)
51+
if err != nil {
52+
t.Fatalf("ReadIndex failed for database with cached index: %v", err)
53+
}
54+
if !reflect.DeepEqual(idx, expectedIdx) {
55+
t.Errorf("ReadIndex returned unexpected index, got:\n%s\nwant:\n%s", idx, expectedIdx)
56+
}
57+
if !retrieved.Equal(now) {
58+
t.Errorf("ReadIndex returned unexpected retrieved: got %s, want %s", retrieved, now)
59+
}
60+
61+
if _, err = cache.ReadEntries(dbName, "vuln.example.com"); err != nil {
62+
t.Fatalf("ReadEntires failed for non-existent package: %v", err)
63+
}
64+
65+
expectedEntries := []*osv.Entry{
66+
&osv.Entry{ID: "001"},
67+
&osv.Entry{ID: "002"},
68+
&osv.Entry{ID: "003"},
69+
}
70+
if err := cache.WriteEntries(dbName, "vuln.example.com", expectedEntries); err != nil {
71+
t.Fatalf("WriteEntries failed: %v", err)
72+
}
73+
74+
entries, err := cache.ReadEntries(dbName, "vuln.example.com")
75+
if err != nil {
76+
t.Fatalf("ReadEntries failed for cached package: %v", err)
77+
}
78+
if !reflect.DeepEqual(entries, expectedEntries) {
79+
t.Errorf("ReadEntries returned unexpected entries, got:\n%v\nwant:\n%v", entries, expectedEntries)
80+
}
81+
}

0 commit comments

Comments
 (0)