2
2
package main
3
3
4
4
import (
5
+ "context"
5
6
"encoding/json"
6
7
"flag"
7
8
"fmt"
8
9
"io"
9
10
"log/slog"
10
11
"net/http"
11
12
"os"
12
- "path"
13
13
"regexp"
14
+ "sort"
14
15
"strings"
16
+ "sync"
17
+ "time"
15
18
19
+ "cloud.google.com/go/storage"
20
+ "github.com/google/osv/vulnfeeds/cves"
16
21
"github.com/google/osv/vulnfeeds/models"
17
22
"github.com/google/osv/vulnfeeds/utility/logger"
18
23
"github.com/google/osv/vulnfeeds/vulns"
24
+ "github.com/ossf/osv-schema/bindings/go/osvschema"
19
25
)
20
26
21
27
const (
22
28
alpineURLBase = "https://secdb.alpinelinux.org/%s/main.json"
23
29
alpineIndexURL = "https://secdb.alpinelinux.org/"
24
- alpineOutputPathDefault = "parts/alpine"
30
+ alpineOutputPathDefault = "alpine"
31
+ defaultCvePath = "cve_jsons"
32
+ outputBucketDefault = "osv-test-cve-osv-conversion"
25
33
)
26
34
27
35
func main () {
@@ -31,15 +39,51 @@ func main() {
31
39
"alpineOutput" ,
32
40
alpineOutputPathDefault ,
33
41
"path to output general alpine affected package information" )
42
+ outputBucketName := flag .String ("output_bucket" , outputBucketDefault , "The GCS bucket to write to." )
43
+ numWorkers := flag .Int ("num_workers" , 64 , "Number of workers to process records" )
44
+ uploadToGCS := flag .Bool ("uploadToGCS" , false , "If true, do not write to GCS bucket and instead write to local disk." )
34
45
flag .Parse ()
35
46
36
47
err := os .MkdirAll (* alpineOutputPath , 0755 )
37
48
if err != nil {
38
49
logger .Fatal ("Can't create output path" , slog .Any ("err" , err ))
39
50
}
40
51
52
+ allCVEs := vulns .LoadAllCVEs (defaultCvePath )
41
53
allAlpineSecDB := getAlpineSecDBData ()
42
- generateAlpineOSV (allAlpineSecDB , * alpineOutputPath )
54
+ osvVulnerabilities := generateAlpineOSV (allAlpineSecDB , allCVEs )
55
+
56
+ ctx := context .Background ()
57
+ var bkt * storage.BucketHandle
58
+ if * uploadToGCS {
59
+ storageClient , err := storage .NewClient (ctx )
60
+ if err != nil {
61
+ logger .Fatal ("Failed to create storage client" , slog .Any ("err" , err ))
62
+ }
63
+ bkt = storageClient .Bucket (* outputBucketName )
64
+ }
65
+ var wg sync.WaitGroup
66
+ vulnChan := make (chan * vulns.Vulnerability )
67
+
68
+ for range * numWorkers {
69
+ wg .Add (1 )
70
+ go func () {
71
+ defer wg .Done ()
72
+ vulns .Worker (ctx , vulnChan , bkt , * alpineOutputPath )
73
+ }()
74
+ }
75
+
76
+ for _ , v := range osvVulnerabilities {
77
+ if len (v .Affected ) == 0 {
78
+ logger .Warn (fmt .Sprintf ("Skipping %s as no affected versions found." , v .ID ), slog .String ("id" , v .ID ))
79
+ continue
80
+ }
81
+ vulnChan <- v
82
+ }
83
+
84
+ close (vulnChan )
85
+ wg .Wait ()
86
+ logger .Info ("Alpine CVE conversion succeeded." )
43
87
}
44
88
45
89
// getAllAlpineVersions gets all available version name in alpine secdb
@@ -55,7 +99,7 @@ func getAllAlpineVersions() []string {
55
99
logger .Fatal ("Failed to get alpine index page" , slog .Any ("err" , err ))
56
100
}
57
101
58
- exp := regexp .MustCompile (" href=\ " (v[\\ d.]*)/\" " )
102
+ exp := regexp .MustCompile (` href="(v[\d.]*)/"` )
59
103
60
104
searchRes := exp .FindAllStringSubmatch (buf .String (), - 1 )
61
105
alpineVersions := make ([]string , 0 , len (searchRes ))
@@ -87,8 +131,9 @@ func getAlpineSecDBData() map[string][]VersionAndPkg {
87
131
cveID = strings .Split (cveID , " " )[0 ]
88
132
89
133
if ! validVersion (version ) {
90
- logger .Warn ("Invalid alpine version" ,
91
- slog .String ("version" , version ),
134
+ logger .Warn (fmt .Sprintf ("[%s] Invalid alpine version: '%s', on package: '%s', and alpine version: '%s'" ,
135
+ cveID , version , pkg .Pkg .Name ,
136
+ alpineVer ), slog .String ("version" , version ),
92
137
slog .String ("package" , pkg .Pkg .Name ),
93
138
slog .String ("alpine_version" , alpineVer ),
94
139
)
@@ -111,9 +156,53 @@ func getAlpineSecDBData() map[string][]VersionAndPkg {
111
156
}
112
157
113
158
// generateAlpineOSV generates the generic PackageInfo package from the information given by alpine advisory
114
- func generateAlpineOSV (allAlpineSecDb map [string ][]VersionAndPkg , alpineOutputPath string ) {
115
- for cveID , verPkgs := range allAlpineSecDb {
116
- pkgInfos := make ([]vulns.PackageInfo , 0 , len (verPkgs ))
159
+ func generateAlpineOSV (allAlpineSecDb map [string ][]VersionAndPkg , allCVEs map [cves.CVEID ]cves.Vulnerability ) (osvVulnerabilities []* vulns.Vulnerability ) {
160
+ cveIDs := make ([]string , 0 , len (allAlpineSecDb ))
161
+ for cveID := range allAlpineSecDb {
162
+ cveIDs = append (cveIDs , cveID )
163
+ }
164
+ sort .Strings (cveIDs )
165
+
166
+ for _ , cveID := range cveIDs {
167
+ verPkgs := allAlpineSecDb [cveID ]
168
+ sort .Slice (verPkgs , func (i , j int ) bool {
169
+ if verPkgs [i ].Pkg != verPkgs [j ].Pkg {
170
+ return verPkgs [i ].Pkg < verPkgs [j ].Pkg
171
+ }
172
+ if verPkgs [i ].AlpineVer != verPkgs [j ].AlpineVer {
173
+ return verPkgs [i ].AlpineVer < verPkgs [j ].AlpineVer
174
+ }
175
+
176
+ return verPkgs [i ].Ver < verPkgs [j ].Ver
177
+ })
178
+ cve , ok := allCVEs [cves .CVEID (cveID )]
179
+ var published time.Time
180
+ var details string
181
+ if ok {
182
+ published = cve .CVE .Published .Time
183
+ if len (cve .CVE .Descriptions ) > 0 {
184
+ details = cve .CVE .Descriptions [0 ].Value
185
+ }
186
+ } else {
187
+ // TODO: add support for non-CVE reports
188
+ logger .Warn (fmt .Sprintf ("CVE %s not found in cve_jsons" , cveID ), slog .String ("cveID" , cveID ))
189
+ continue
190
+ }
191
+
192
+ v := & vulns.Vulnerability {
193
+ Vulnerability : osvschema.Vulnerability {
194
+ ID : "ALPINE-" + cveID ,
195
+ Upstream : []string {cveID },
196
+ Published : published ,
197
+ Details : details ,
198
+ References : []osvschema.Reference {
199
+ {
200
+ Type : "ADVISORY" ,
201
+ URL : "https://security.alpinelinux.org/vuln/" + cveID ,
202
+ },
203
+ },
204
+ },
205
+ }
117
206
118
207
for _ , verPkg := range verPkgs {
119
208
pkgInfo := vulns.PackageInfo {
@@ -124,23 +213,17 @@ func generateAlpineOSV(allAlpineSecDb map[string][]VersionAndPkg, alpineOutputPa
124
213
Ecosystem : "Alpine:" + verPkg .AlpineVer ,
125
214
PURL : "pkg:apk/alpine/" + verPkg .Pkg + "?arch=source" ,
126
215
}
127
- pkgInfos = append ( pkgInfos , pkgInfo )
216
+ v . AddPkgInfo ( pkgInfo )
128
217
}
129
218
130
- file , err := os .OpenFile (path .Join (alpineOutputPath , cveID + ".alpine.json" ), os .O_CREATE | os .O_RDWR , 0644 )
131
- if err != nil {
132
- logger .Fatal ("Failed to create/write osv output file" , slog .Any ("err" , err ))
133
- }
134
- encoder := json .NewEncoder (file )
135
- encoder .SetIndent ("" , " " )
136
- err = encoder .Encode (& pkgInfos )
137
- if err != nil {
138
- logger .Fatal ("Failed to encode package info output file" , slog .Any ("err" , err ))
219
+ if len (v .Affected ) == 0 {
220
+ logger .Warn (fmt .Sprintf ("Skipping %s as no affected versions found." , v .ID ), slog .String ("cveID" , cveID ))
221
+ continue
139
222
}
140
- _ = file . Close ( )
223
+ osvVulnerabilities = append ( osvVulnerabilities , v )
141
224
}
142
225
143
- logger . Info ( "Finished" )
226
+ return osvVulnerabilities
144
227
}
145
228
146
229
// downloadAlpine downloads Alpine SecDB data from their API
0 commit comments