-
Notifications
You must be signed in to change notification settings - Fork 304
Expand file tree
/
Copy pathmain.go
More file actions
241 lines (202 loc) · 7 KB
/
main.go
File metadata and controls
241 lines (202 loc) · 7 KB
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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
// package main contains the conversion logic for turning debian security tracker info to OSV parts
package main
import (
"context"
"encoding/csv"
"encoding/json"
"flag"
"fmt"
"log/slog"
"net/http"
"os"
"sort"
"strconv"
"strings"
"github.com/google/osv/vulnfeeds/faulttolerant"
"github.com/google/osv/vulnfeeds/gcs-tools"
"github.com/google/osv/vulnfeeds/models"
"github.com/google/osv/vulnfeeds/utility/logger"
"github.com/google/osv/vulnfeeds/vulns"
"github.com/ossf/osv-schema/bindings/go/osvschema"
"google.golang.org/protobuf/types/known/timestamppb"
)
const (
defaultCvePath = "cve_jsons"
debianOutputPathDefault = "debian-cve-osv"
debianDistroInfoURL = "https://debian.pages.debian.net/distro-info-data/debian.csv"
debianSecurityTrackerURL = "https://security-tracker.debian.org/tracker/data/json"
outputBucketDefault = "debian-osv"
)
func main() {
logger.InitGlobalLogger()
defer logger.Close()
debianOutputPath := flag.String("output-path", debianOutputPathDefault, "Path to output OSV files.")
outputBucketName := flag.String("output-bucket", outputBucketDefault, "The GCS bucket to write to.")
numWorkers := flag.Int("workers", 64, "Number of workers to process records")
uploadToGCS := flag.Bool("upload-to-gcs", false, "If true, do not write to GCS bucket and instead write to local disk.")
syncDeletions := flag.Bool("sync-deletions", false, "If false, do not delete files in bucket that are not local")
flag.Parse()
err := os.MkdirAll(*debianOutputPath, 0755)
if err != nil {
logger.Fatal("Can't create output path", slog.Any("err", err))
}
debianData, err := downloadDebianSecurityTracker()
if err != nil {
logger.Fatal("Failed to download/parse Debian Security Tracker json file", slog.Any("err", err))
}
debianReleaseMap, err := getDebianReleaseMap()
if err != nil {
logger.Fatal("Failed to get Debian distro info data", slog.Any("err", err))
}
allCVEs := vulns.LoadAllCVEs(defaultCvePath)
osvCVEs := generateOSVFromDebianTracker(debianData, debianReleaseMap, allCVEs)
vulnerabilities := make([]*osvschema.Vulnerability, 0, len(osvCVEs))
for _, v := range osvCVEs {
if len(v.Affected) == 0 {
logger.Warn(fmt.Sprintf("Skipping %s as no affected versions found.", v.Id), slog.String("id", v.Id))
continue
}
vulnerabilities = append(vulnerabilities, v.Vulnerability)
}
ctx := context.Background()
gcs.Upload(ctx, "Debian CVEs", *uploadToGCS, *outputBucketName, "", *numWorkers, *debianOutputPath, vulnerabilities, *syncDeletions)
logger.Info("Debian CVE conversion succeeded.")
}
// generateOSVFromDebianTracker converts Debian Security Tracker entries to OSV format.
func generateOSVFromDebianTracker(debianData DebianSecurityTrackerData, debianReleaseMap map[string]string, allCVEs map[models.CVEID]models.Vulnerability) map[string]*vulns.Vulnerability {
logger.Info("Converting Debian Security Tracker data to OSV.")
osvCves := make(map[string]*vulns.Vulnerability)
// Sorts packages to ensure results remain consistent between runs.
pkgNames := make([]string, 0, len(debianData))
for name := range debianData {
pkgNames = append(pkgNames, name)
}
sort.Strings(pkgNames)
// Sorts releases to ensure pkgInfos remain consistent between runs.
releaseNames := make([]string, 0, len(debianReleaseMap))
for k := range debianReleaseMap {
releaseNames = append(releaseNames, k)
}
sort.Slice(releaseNames, func(i, j int) bool {
vi, _ := strconv.ParseFloat(debianReleaseMap[releaseNames[i]], 64)
vj, _ := strconv.ParseFloat(debianReleaseMap[releaseNames[j]], 64)
return vi < vj
})
for _, pkgName := range pkgNames {
pkg := debianData[pkgName]
for cveID, cveData := range pkg {
// Debian Security Tracker has some 'TEMP-' Records we don't want to convert
if !strings.HasPrefix(cveID, "CVE") {
continue
}
v, ok := osvCves[cveID]
currentNVDCVE := allCVEs[models.CVEID(cveID)]
if !ok {
v = &vulns.Vulnerability{
Vulnerability: &osvschema.Vulnerability{
Id: "DEBIAN-" + cveID,
Upstream: []string{cveID},
Published: timestamppb.New(currentNVDCVE.CVE.Published.Time),
Details: cveData.Description,
References: []*osvschema.Reference{
{
Type: osvschema.Reference_ADVISORY,
Url: "https://security-tracker.debian.org/tracker/" + cveID,
},
},
},
}
if currentNVDCVE.CVE.Metrics != nil {
v.AddSeverity(currentNVDCVE.CVE.Metrics)
}
osvCves[cveID] = v
}
for _, releaseName := range releaseNames {
// For reference on urgency levels, see: https://security-team.debian.org/security_tracker.html#severity-levels
release, ok := cveData.Releases[releaseName]
if !ok {
continue
}
debianVersion, ok := debianReleaseMap[releaseName]
if !ok {
continue
}
if release.Status == "resolved" && release.FixedVersion == "0" { // not affected
continue
}
pkgInfo := vulns.PackageInfo{
PkgName: pkgName,
Ecosystem: "Debian:" + debianVersion,
EcosystemSpecific: map[string]any{
"urgency": release.Urgency,
},
}
if release.Status == "resolved" {
pkgInfo.VersionInfo.AffectedVersions = []models.AffectedVersion{{Introduced: "0"}, {Fixed: release.FixedVersion}}
} else {
pkgInfo.VersionInfo.AffectedVersions = []models.AffectedVersion{{Introduced: "0"}}
}
if len(pkgInfo.VersionInfo.AffectedVersions) > 0 {
v.AddPkgInfo(pkgInfo)
}
}
}
}
return osvCves
}
// getDebianReleaseMap gets the Debian version number, excluding testing and experimental versions.
func getDebianReleaseMap() (map[string]string, error) {
releaseMap := make(map[string]string)
res, err := faulttolerant.Get(debianDistroInfoURL)
if err != nil {
return releaseMap, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return releaseMap, fmt.Errorf("HTTP request failed: %s", res.Status)
}
reader := csv.NewReader(res.Body)
reader.FieldsPerRecord = -1
data, err := reader.ReadAll()
if err != nil {
return releaseMap, err
}
versionIndex := -1
seriesIndex := -1
// Get the index number of version and series.
for i, col := range data[0] {
switch col {
case "version":
versionIndex = i
case "series":
seriesIndex = i
}
}
if seriesIndex == -1 || versionIndex == -1 {
return releaseMap, err
}
for _, row := range data[1:] {
if row[versionIndex] == "" {
continue
}
releaseMap[row[seriesIndex]] = row[versionIndex]
}
return releaseMap, err
}
// downloadDebianSecurityTracker download Debian json file
func downloadDebianSecurityTracker() (DebianSecurityTrackerData, error) {
res, err := faulttolerant.Get(debianSecurityTrackerURL)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP request failed: %s", res.Status)
}
var decodedDebianData DebianSecurityTrackerData
if err := json.NewDecoder(res.Body).Decode(&decodedDebianData); err != nil {
return nil, err
}
logger.Info("Successfully downloaded Debian Security Tracker Data")
return decodedDebianData, err
}