-
Notifications
You must be signed in to change notification settings - Fork 8
/
kev.go
120 lines (100 loc) · 3.12 KB
/
kev.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package kev
import (
"bytes"
"encoding/json"
"errors"
"io"
"log/slog"
"net/http"
"time"
"github.com/dustin/go-humanize"
)
const DefaultBaseURL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
// Catalog data model for KEVs
type Catalog struct {
Title string `json:"title"`
CatalogVersion string `json:"catalogVersion"`
DateReleased time.Time `json:"dateReleased"`
Count int `json:"count"`
Vulnerabilities []Vulnerability `json:"vulnerabilities"`
}
// Vulnerability data model for a single record
type Vulnerability struct {
CveID string `json:"cveID"`
VendorProject string `json:"vendorProject"`
Product string `json:"product"`
VulnerabilityName string `json:"vulnerabilityName"`
DateAdded string `json:"dateAdded"`
ShortDescription string `json:"shortDescription"`
RequiredAction string `json:"requiredAction"`
DueDate string `json:"dueDate"`
Notes string `json:"notes"`
}
func NewCatalog() *Catalog {
return &Catalog{
Vulnerabilities: make([]Vulnerability, 0),
}
}
type FetchOptions struct {
Client *http.Client
URL string
}
type fetchOptionFunc func(*FetchOptions)
func WithURL(url string) fetchOptionFunc {
return func(o *FetchOptions) {
o.URL = url
}
}
func WithClient(client *http.Client) fetchOptionFunc {
return func(o *FetchOptions) {
o.Client = client
}
}
func DefaultFetchOptions() *FetchOptions {
return &FetchOptions{
Client: http.DefaultClient,
URL: DefaultBaseURL,
}
}
func DownloadData(w io.Writer, optionFuncs ...fetchOptionFunc) error {
options := DefaultFetchOptions()
for _, optionFunc := range optionFuncs {
optionFunc(options)
}
logger := slog.Default().With("method", "GET", "url", options.URL)
defer func(started time.Time) {
logger.Debug("kev json fetch done", "elapsed", time.Since(started))
}(time.Now())
logger.Debug("request kev data from api")
res, err := options.Client.Get(options.URL)
switch {
case err != nil:
logger.Error("kev api request failed during fetch data", "error", err)
return errors.New("failed to get KEV Catalog. see log for details")
case res.StatusCode != http.StatusOK:
logger.Error("kev api bad status code", "res_status", res.Status)
return errors.New("failed to get KEV Catalog. see log for details")
}
n, err := io.Copy(w, res.Body)
size := humanize.Bytes(uint64(n))
if err != nil {
logger.Error("io copy to writer from res body", "error", err)
return errors.New("failed to get EPSS Scores. see log for details")
}
slog.Debug("successfully downloaded and decompressed epss data", "decompressed_size", size)
return err
}
func FetchData(catalog *Catalog, optionFuncs ...fetchOptionFunc) error {
buf := new(bytes.Buffer)
if err := DownloadData(buf, optionFuncs...); err != nil {
return err
}
return DecodeData(buf, catalog)
}
func DecodeData(r io.Reader, catalog *Catalog) error {
if err := json.NewDecoder(r).Decode(catalog); err != nil {
slog.Error("kev decoding failure", "error", err)
return errors.New("failed to get KEV Catalog. see log for details")
}
return nil
}