Skip to content

Commit 27a98cf

Browse files
authored
fix(vulnfeeds): Migrate to latest OSV schema bindings (#4241)
Unblocks #4190 This PR updates `vulnfeeds` to use the latest version of `github.com/ossf/osv-schema/bindings/go`. To better handle ecosystem and database specific fields, a new function `NewStructpbFromMap` is added to `utility` to convert `map[string]any` to `structpb.Struct` so we don't need to worry about conversion every time.
1 parent c632cbb commit 27a98cf

File tree

25 files changed

+1195
-878
lines changed

25 files changed

+1195
-878
lines changed

vulnfeeds/cmd/alpine/main.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/google/osv/vulnfeeds/utility/logger"
2222
"github.com/google/osv/vulnfeeds/vulns"
2323
"github.com/ossf/osv-schema/bindings/go/osvschema"
24+
"google.golang.org/protobuf/types/known/timestamppb"
2425
)
2526

2627
const (
@@ -56,10 +57,10 @@ func main() {
5657
vulnerabilities := make([]*osvschema.Vulnerability, 0, len(osvVulnerabilities))
5758
for _, v := range osvVulnerabilities {
5859
if len(v.Affected) == 0 {
59-
logger.Warn(fmt.Sprintf("Skipping %s as no affected versions found.", v.ID), slog.String("id", v.ID))
60+
logger.Warn(fmt.Sprintf("Skipping %s as no affected versions found.", v.Id), slog.String("id", v.Id))
6061
continue
6162
}
62-
vulnerabilities = append(vulnerabilities, &v.Vulnerability)
63+
vulnerabilities = append(vulnerabilities, v.Vulnerability)
6364
}
6465

6566
ctx := context.Background()
@@ -171,15 +172,15 @@ func generateAlpineOSV(allAlpineSecDb map[string][]VersionAndPkg, allCVEs map[cv
171172
}
172173

173174
v := &vulns.Vulnerability{
174-
Vulnerability: osvschema.Vulnerability{
175-
ID: "ALPINE-" + cveID,
175+
Vulnerability: &osvschema.Vulnerability{
176+
Id: "ALPINE-" + cveID,
176177
Upstream: []string{cveID},
177-
Published: published,
178+
Published: timestamppb.New(published),
178179
Details: details,
179-
References: []osvschema.Reference{
180+
References: []*osvschema.Reference{
180181
{
181-
Type: "ADVISORY",
182-
URL: "https://security.alpinelinux.org/vuln/" + cveID,
182+
Type: osvschema.Reference_ADVISORY,
183+
Url: "https://security.alpinelinux.org/vuln/" + cveID,
183184
},
184185
},
185186
},
@@ -198,7 +199,7 @@ func generateAlpineOSV(allAlpineSecDb map[string][]VersionAndPkg, allCVEs map[cv
198199
}
199200

200201
if len(v.Affected) == 0 {
201-
logger.Warn(fmt.Sprintf("Skipping %s as no affected versions found.", v.ID), slog.String("cveID", cveID))
202+
logger.Warn(fmt.Sprintf("Skipping %s as no affected versions found.", v.Id), slog.String("cveID", cveID))
202203
continue
203204
}
204205
if cve.CVE.Metrics != nil {

vulnfeeds/cmd/combine-to-osv/main.go

Lines changed: 50 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package main
33

44
import (
55
"context"
6-
"encoding/json"
76
"errors"
87
"flag"
98
"fmt"
@@ -21,6 +20,7 @@ import (
2120
"github.com/google/osv/vulnfeeds/utility/logger"
2221
"github.com/ossf/osv-schema/bindings/go/osvschema"
2322
"google.golang.org/api/iterator"
23+
"google.golang.org/protobuf/encoding/protojson"
2424
)
2525

2626
const (
@@ -87,7 +87,7 @@ func main() {
8787

8888
vulnerabilities := make([]*osvschema.Vulnerability, 0, len(combinedData))
8989
for _, v := range combinedData {
90-
vulnerabilities = append(vulnerabilities, &v)
90+
vulnerabilities = append(vulnerabilities, v)
9191
}
9292

9393
upload.Upload(ctx, "OSV files", *uploadToGCS, *outputBucketName, *overridesBucketName, *numWorkers, *osvOutputPath, vulnerabilities, *syncDeletions)
@@ -137,8 +137,8 @@ func listBucketObjects(bucketName string, prefix string) ([]string, error) {
137137
// The function returns a map of CVE IDs to their corresponding Vulnerability objects.
138138
// Files that are not ".json" files, directories, or files ending in ".metrics.json" are skipped.
139139
// The function will log warnings for files that fail to open or decode, and will terminate if it fails to walk the directory.
140-
func loadOSV(osvPath string) map[cves.CVEID]osvschema.Vulnerability {
141-
allVulns := make(map[cves.CVEID]osvschema.Vulnerability)
140+
func loadOSV(osvPath string) map[cves.CVEID]*osvschema.Vulnerability {
141+
allVulns := make(map[cves.CVEID]*osvschema.Vulnerability)
142142
logger.Info("Loading OSV records", slog.String("path", osvPath))
143143
err := filepath.WalkDir(osvPath, func(path string, d fs.DirEntry, err error) error {
144144
if err != nil {
@@ -148,20 +148,19 @@ func loadOSV(osvPath string) map[cves.CVEID]osvschema.Vulnerability {
148148
return nil
149149
}
150150

151-
file, err := os.Open(path)
151+
file, err := os.ReadFile(path)
152152
if err != nil {
153153
logger.Warn("Failed to open OSV JSON file", slog.String("path", path), slog.Any("err", err))
154154
return nil
155155
}
156156

157157
var vuln osvschema.Vulnerability
158-
decodeErr := json.NewDecoder(file).Decode(&vuln)
159-
file.Close()
158+
decodeErr := protojson.Unmarshal(file, &vuln)
160159
if decodeErr != nil {
161160
logger.Error("Failed to decode, skipping", slog.String("file", path), slog.Any("err", decodeErr))
162161
return nil
163162
}
164-
allVulns[cves.CVEID(vuln.ID)] = vuln
163+
allVulns[cves.CVEID(vuln.GetId())] = &vuln
165164

166165
return nil
167166
})
@@ -174,12 +173,12 @@ func loadOSV(osvPath string) map[cves.CVEID]osvschema.Vulnerability {
174173
}
175174

176175
// combineIntoOSV creates OSV entry by combining loaded CVEs from NVD and PackageInfo information from security advisories.
177-
func combineIntoOSV(cve5osv map[cves.CVEID]osvschema.Vulnerability, nvdosv map[cves.CVEID]osvschema.Vulnerability, mandatoryCVEIDs []string) map[cves.CVEID]osvschema.Vulnerability {
178-
osvRecords := make(map[cves.CVEID]osvschema.Vulnerability)
176+
func combineIntoOSV(cve5osv map[cves.CVEID]*osvschema.Vulnerability, nvdosv map[cves.CVEID]*osvschema.Vulnerability, mandatoryCVEIDs []string) map[cves.CVEID]*osvschema.Vulnerability {
177+
osvRecords := make(map[cves.CVEID]*osvschema.Vulnerability)
179178

180179
// Iterate through CVEs from security advisories (cve5) as the base
181180
for cveID, cve5 := range cve5osv {
182-
var baseOSV osvschema.Vulnerability
181+
var baseOSV *osvschema.Vulnerability
183182
nvd, ok := nvdosv[cveID]
184183

185184
if ok {
@@ -190,7 +189,7 @@ func combineIntoOSV(cve5osv map[cves.CVEID]osvschema.Vulnerability, nvdosv map[c
190189
baseOSV = cve5
191190
}
192191

193-
if len(baseOSV.Affected) == 0 {
192+
if len(baseOSV.GetAffected()) == 0 {
194193
// check if part exists.
195194
if !slices.Contains(mandatoryCVEIDs, string(cveID)) {
196195
continue
@@ -201,7 +200,7 @@ func combineIntoOSV(cve5osv map[cves.CVEID]osvschema.Vulnerability, nvdosv map[c
201200

202201
// Add any remaining CVEs from NVD that were not in the advisory data.
203202
for cveID, nvd := range nvdosv {
204-
if len(nvd.Affected) == 0 {
203+
if len(nvd.GetAffected()) == 0 {
205204
continue
206205
}
207206
osvRecords[cveID] = nvd
@@ -211,40 +210,40 @@ func combineIntoOSV(cve5osv map[cves.CVEID]osvschema.Vulnerability, nvdosv map[c
211210
}
212211

213212
// combineTwoOSVRecords takes two osv records and combines them into one
214-
func combineTwoOSVRecords(cve5 osvschema.Vulnerability, nvd osvschema.Vulnerability) osvschema.Vulnerability {
213+
func combineTwoOSVRecords(cve5 *osvschema.Vulnerability, nvd *osvschema.Vulnerability) *osvschema.Vulnerability {
215214
baseOSV := cve5
216-
combinedAffected := pickAffectedInformation(cve5.Affected, nvd.Affected)
215+
combinedAffected := pickAffectedInformation(cve5.GetAffected(), nvd.GetAffected())
217216

218217
baseOSV.Affected = combinedAffected
219218
// Merge references, ensuring no duplicates.
220219
refMap := make(map[string]bool)
221-
for _, r := range baseOSV.References {
222-
refMap[r.URL] = true
220+
for _, r := range baseOSV.GetReferences() {
221+
refMap[r.GetUrl()] = true
223222
}
224-
for _, r := range nvd.References {
225-
if !refMap[r.URL] {
223+
for _, r := range nvd.GetReferences() {
224+
if !refMap[r.GetUrl()] {
226225
baseOSV.References = append(baseOSV.References, r)
227-
refMap[r.URL] = true
226+
refMap[r.GetUrl()] = true
228227
}
229228
}
230229

231230
// Merge timestamps: latest modified, earliest published.
232-
cve5Modified := baseOSV.Modified
233-
if nvd.Modified.After(cve5Modified) {
234-
baseOSV.Modified = nvd.Modified
231+
cve5Modified := baseOSV.GetModified()
232+
if nvd.GetModified().AsTime().After(cve5Modified.AsTime()) {
233+
baseOSV.Modified = nvd.GetModified()
235234
}
236235

237-
cve5Published := baseOSV.Published
238-
if nvd.Published.Before(cve5Published) {
239-
baseOSV.Published = nvd.Published
236+
cve5Published := baseOSV.GetPublished()
237+
if nvd.GetPublished().AsTime().Before(cve5Published.AsTime()) {
238+
baseOSV.Published = nvd.GetPublished()
240239
}
241240

242241
// Merge aliases, ensuring no duplicates.
243242
aliasMap := make(map[string]bool)
244-
for _, alias := range baseOSV.Aliases {
243+
for _, alias := range baseOSV.GetAliases() {
245244
aliasMap[alias] = true
246245
}
247-
for _, alias := range nvd.Aliases {
246+
for _, alias := range nvd.GetAliases() {
248247
if !aliasMap[alias] {
249248
baseOSV.Aliases = append(baseOSV.Aliases, alias)
250249
aliasMap[alias] = true
@@ -259,7 +258,7 @@ func combineTwoOSVRecords(cve5 osvschema.Vulnerability, nvd osvschema.Vulnerabil
259258
// If a match is found, it merges the version range information, preferring the entry
260259
// with more ranges. Unmatched nvdAffected packages are appended.
261260
// It returns a new slice and does not modify cve5Affected in place.
262-
func pickAffectedInformation(cve5Affected []osvschema.Affected, nvdAffected []osvschema.Affected) []osvschema.Affected {
261+
func pickAffectedInformation(cve5Affected []*osvschema.Affected, nvdAffected []*osvschema.Affected) []*osvschema.Affected {
263262
if len(nvdAffected) == 0 {
264263
return cve5Affected
265264
}
@@ -268,31 +267,31 @@ func pickAffectedInformation(cve5Affected []osvschema.Affected, nvdAffected []os
268267
return nvdAffected
269268
}
270269

271-
nvdRepoMap := make(map[string][]osvschema.Range)
270+
nvdRepoMap := make(map[string][]*osvschema.Range)
272271
for _, affected := range nvdAffected {
273-
for _, r := range affected.Ranges {
274-
if r.Repo != "" {
275-
repo := strings.ToLower(r.Repo)
272+
for _, r := range affected.GetRanges() {
273+
if r.GetRepo() != "" {
274+
repo := strings.ToLower(r.GetRepo())
276275
nvdRepoMap[repo] = append(nvdRepoMap[repo], r)
277276
}
278277
}
279278
}
280279

281-
cve5RepoMap := make(map[string][]osvschema.Range)
280+
cve5RepoMap := make(map[string][]*osvschema.Range)
282281
for _, affected := range cve5Affected {
283-
for _, r := range affected.Ranges {
284-
if r.Repo != "" {
285-
repo := strings.ToLower(r.Repo)
282+
for _, r := range affected.GetRanges() {
283+
if r.GetRepo() != "" {
284+
repo := strings.ToLower(r.GetRepo())
286285
cve5RepoMap[repo] = append(cve5RepoMap[repo], r)
287286
}
288287
}
289288
}
290289

291-
newRepoAffectedMap := make(map[string]osvschema.Affected)
290+
newRepoAffectedMap := make(map[string]*osvschema.Affected)
292291

293292
for repo, cveRanges := range cve5RepoMap {
294293
if nvdRanges, ok := nvdRepoMap[repo]; ok {
295-
var newAffectedRanges []osvschema.Range
294+
var newAffectedRanges []*osvschema.Range
296295

297296
// Found a match. If NVD has more ranges, use its ranges.
298297
if len(nvdRanges) > len(cveRanges) {
@@ -301,8 +300,8 @@ func pickAffectedInformation(cve5Affected []osvschema.Affected, nvdAffected []os
301300
} else if len(nvdRanges) < len(cveRanges) {
302301
newAffectedRanges = cveRanges
303302
} else if len(cveRanges) == 1 && len(nvdRanges) == 1 {
304-
c5Intro, c5Fixed := getRangeBoundaryVersions(cveRanges[0].Events)
305-
nvdIntro, nvdFixed := getRangeBoundaryVersions(nvdRanges[0].Events)
303+
c5Intro, c5Fixed := getRangeBoundaryVersions(cveRanges[0].GetEvents())
304+
nvdIntro, nvdFixed := getRangeBoundaryVersions(nvdRanges[0].GetEvents())
306305

307306
// Prefer cve5 data, but use nvd data if cve5 data is missing.
308307
if c5Intro == "" {
@@ -315,30 +314,30 @@ func pickAffectedInformation(cve5Affected []osvschema.Affected, nvdAffected []os
315314
if c5Intro != "" || c5Fixed != "" {
316315
newRange := cves.BuildVersionRange(c5Intro, "", c5Fixed)
317316
newRange.Repo = repo
318-
newRange.Type = osvschema.RangeGit // Preserve the repo
317+
newRange.Type = osvschema.Range_GIT // Preserve the repo
319318
newAffectedRanges = append(newAffectedRanges, newRange)
320319
}
321320
}
322321
// Remove from map so we know which NVD packages are left.
323322
delete(nvdRepoMap, repo)
324-
newRepoAffectedMap[repo] = osvschema.Affected{
323+
newRepoAffectedMap[repo] = &osvschema.Affected{
325324
Ranges: newAffectedRanges,
326325
}
327326
} else {
328-
newRepoAffectedMap[repo] = osvschema.Affected{
327+
newRepoAffectedMap[repo] = &osvschema.Affected{
329328
Ranges: cveRanges,
330329
}
331330
}
332331
}
333332

334333
// Add remaining NVD packages that were not in cve5.
335334
for repo, nvdRange := range nvdRepoMap {
336-
newRepoAffectedMap[repo] = osvschema.Affected{
335+
newRepoAffectedMap[repo] = &osvschema.Affected{
337336
Ranges: nvdRange,
338337
}
339338
}
340339

341-
var combinedAffected []osvschema.Affected //nolint:prealloc
340+
var combinedAffected []*osvschema.Affected //nolint:prealloc
342341
for _, aff := range newRepoAffectedMap {
343342
combinedAffected = append(combinedAffected, aff)
344343
}
@@ -348,13 +347,13 @@ func pickAffectedInformation(cve5Affected []osvschema.Affected, nvdAffected []os
348347

349348
// getRangeBoundaryVersions extracts the introduced and fixed versions from a slice of OSV events.
350349
// It iterates through the events and returns the last non-empty "introduced" and "fixed" versions found.
351-
func getRangeBoundaryVersions(events []osvschema.Event) (introduced, fixed string) {
350+
func getRangeBoundaryVersions(events []*osvschema.Event) (introduced, fixed string) {
352351
for _, e := range events {
353-
if e.Introduced != "0" && e.Introduced != "" {
354-
introduced = e.Introduced
352+
if e.GetIntroduced() != "0" && e.GetIntroduced() != "" {
353+
introduced = e.GetIntroduced()
355354
}
356-
if e.Fixed != "" {
357-
fixed = e.Fixed
355+
if e.GetFixed() != "" {
356+
fixed = e.GetFixed()
358357
}
359358
}
360359

0 commit comments

Comments
 (0)