-
Notifications
You must be signed in to change notification settings - Fork 79
/
updater.go
216 lines (195 loc) · 5.54 KB
/
updater.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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
package ubuntu
import (
"compress/bzip2"
"context"
"encoding/xml"
"fmt"
"io"
"net/http"
"net/url"
"github.com/quay/goval-parser/oval"
"github.com/quay/zlog"
"github.com/quay/claircore"
"github.com/quay/claircore/internal/xmlutil"
"github.com/quay/claircore/libvuln/driver"
"github.com/quay/claircore/pkg/ovalutil"
"github.com/quay/claircore/pkg/tmp"
)
var (
_ driver.Updater = (*updater)(nil)
_ driver.Configurable = (*updater)(nil)
)
// Updater fetches and parses Ubuntu-flavored OVAL.
//
// Updaters are constructed exclusively by the [Factory].
type updater struct {
// the url to fetch the OVAL db from
url string
useBzip2 bool
name string
id string
c *http.Client
}
// Name implements [driver.Updater].
func (u *updater) Name() string {
return fmt.Sprintf("ubuntu/updater/%s", u.name)
}
// Configure implements [driver.Configurable].
func (u *updater) Configure(ctx context.Context, f driver.ConfigUnmarshaler, c *http.Client) error {
ctx = zlog.ContextWithValues(ctx,
"component", "ubuntu/Updater.Configure",
"updater", u.Name())
u.c = c
var cfg UpdaterConfig
if err := f(&cfg); err != nil {
return err
}
if cfg.URL != "" {
if _, err := url.Parse(cfg.URL); err != nil {
return err
}
u.url = cfg.URL
zlog.Info(ctx).
Msg("configured database URL")
}
if cfg.UseBzip2 != nil {
u.useBzip2 = *cfg.UseBzip2
}
return nil
}
// UpdaterConfig is the configuration for the updater.
//
// By convention, this is in a map called "ubuntu/updater/${RELEASE}", e.g.
// "ubuntu/updater/focal".
type UpdaterConfig struct {
URL string `json:"url" yaml:"url"`
UseBzip2 *bool `json:"use_bzip2" yaml:"use_bzip2"`
}
// Fetch implements [driver.Updater].
func (u *updater) Fetch(ctx context.Context, fingerprint driver.Fingerprint) (io.ReadCloser, driver.Fingerprint, error) {
ctx = zlog.ContextWithValues(ctx,
"component", "ubuntu/Updater.Fetch",
"database", u.url)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.url, nil)
if err != nil {
return nil, "", fmt.Errorf("failed to create request")
}
if fingerprint != "" {
req.Header.Set("if-none-match", string(fingerprint))
}
// fetch OVAL xml database
resp, err := u.c.Do(req)
if err != nil {
return nil, "", fmt.Errorf("ubuntu: failed to retrieve OVAL database: %w", err)
}
defer resp.Body.Close()
switch resp.StatusCode {
case http.StatusOK:
if fp := string(fingerprint); fp == "" || fp != resp.Header.Get("etag") {
zlog.Info(ctx).Msg("fetching latest oval database")
break
}
fallthrough
case http.StatusNotModified:
return nil, fingerprint, driver.Unchanged
default:
return nil, "", fmt.Errorf("ubuntu: unexpected response: %s", resp.Status)
}
fp := resp.Header.Get("etag")
f, err := tmp.NewFile("", "ubuntu.")
if err != nil {
return nil, "", err
}
var success bool
defer func() {
if !success {
if err := f.Close(); err != nil {
zlog.Warn(ctx).Err(err).Msg("unable to close spool")
}
}
}()
var r io.Reader = resp.Body
if u.useBzip2 {
r = bzip2.NewReader(r)
}
if _, err := io.Copy(f, r); err != nil {
return nil, "", fmt.Errorf("ubuntu: failed to read http body: %w", err)
}
if _, err := f.Seek(0, io.SeekStart); err != nil {
return nil, "", fmt.Errorf("ubuntu: failed to seek body: %w", err)
}
success = true
zlog.Info(ctx).Msg("fetched latest oval database successfully")
return f, driver.Fingerprint(fp), err
}
// Parse implements [driver.Updater].
func (u *updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vulnerability, error) {
ctx = zlog.ContextWithValues(ctx,
"component", "ubuntu/Updater.Parse")
zlog.Info(ctx).Msg("starting parse")
defer r.Close()
root := oval.Root{}
dec := xml.NewDecoder(r)
dec.CharsetReader = xmlutil.CharsetReader
if err := dec.Decode(&root); err != nil {
return nil, fmt.Errorf("ubuntu: unable to decode OVAL document: %w", err)
}
zlog.Debug(ctx).Msg("xml decoded")
nameLookupFunc := func(def oval.Definition, name *oval.DpkgName) []string {
// if the dpkginfo_object>name field has a var_ref it indicates
// a variable lookup for all packages affected by this vuln is necessary.
//
// if the name.Ref field is empty it indicates a single package is affected
// by the vuln and that package's name is in name.Body.
var ns []string
if len(name.Ref) == 0 {
ns = append(ns, name.Body)
return ns
}
_, i, err := root.Variables.Lookup(name.Ref)
if err != nil {
zlog.Error(ctx).Err(err).Msg("could not lookup variable id")
return ns
}
consts := root.Variables.ConstantVariables[i]
for _, v := range consts.Values {
ns = append(ns, v.Body)
}
return ns
}
protoVulns := func(def oval.Definition) ([]*claircore.Vulnerability, error) {
vs := []*claircore.Vulnerability{}
v := &claircore.Vulnerability{
Updater: u.Name(),
Name: def.Title,
Description: def.Description,
Issued: def.Advisory.Issued.Date,
Links: ovalutil.Links(def),
NormalizedSeverity: normalizeSeverity(def.Advisory.Severity),
Dist: lookupDist(u.id),
}
vs = append(vs, v)
return vs, nil
}
vulns, err := ovalutil.DpkgDefsToVulns(ctx, &root, protoVulns, nameLookupFunc)
if err != nil {
return nil, err
}
return vulns, nil
}
func normalizeSeverity(severity string) claircore.Severity {
switch severity {
case "Negligible":
return claircore.Negligible
case "Low":
return claircore.Low
case "Medium":
return claircore.Medium
case "High":
return claircore.High
case "Critical":
return claircore.Critical
default:
}
return claircore.Unknown
}