@@ -18,6 +18,7 @@ import (
18
18
"strings"
19
19
"time"
20
20
21
+ "github.com/google/go-cmp/cmp"
21
22
"golang.org/x/vulndb/internal/cveclient"
22
23
"golang.org/x/vulndb/internal/cveschema5"
23
24
"golang.org/x/vulndb/internal/report"
43
44
reserveSequential = flag .Bool ("seq" , true , "reserve: if true, reserve new CVE ID batches in sequence" )
44
45
45
46
// flags for the list command
46
- listState = flag .String ("state" , "" , "list: filter by CVE state (RESERVED, PUBLIC, or REJECT)" )
47
-
48
- // flags for the publish command
49
- publishUpdate = flag .Bool ("update" , false , "publish: if true, update an existing CVE Record" )
47
+ listState = flag .String ("state" , "" , "list: filter by CVE state (RESERVED, PUBLISHED, or REJECTED)" )
50
48
51
49
// flags that apply to multiple commands
52
50
year = flag .Int ("year" , 0 , "reserve: the CVE ID year for newly reserved CVE IDs (default is current year)\n list: filter by the year in the CVE ID" )
@@ -62,7 +60,7 @@ func main() {
62
60
fmt .Fprintf (out , formatCmd , "quota" , "outputs the CVE ID quota of the authenticated organization" )
63
61
fmt .Fprintf (out , formatCmd , "id {cve-id}" , "outputs details on an assigned CVE ID (CVE-YYYY-NNNN)" )
64
62
fmt .Fprintf (out , formatCmd , "record {cve-id}" , "outputs the record associated with a CVE ID (CVE-YYYY-NNNN)" )
65
- fmt .Fprintf (out , formatCmd , "[-update] publish {filename}" , "publishes a CVE Record from a YAML or JSON file" )
63
+ fmt .Fprintf (out , formatCmd , "publish {filename}" , "publishes or updates a CVE Record from a YAML or JSON file" )
66
64
fmt .Fprintf (out , formatCmd , "org" , "outputs details on the authenticated organization" )
67
65
fmt .Fprintf (out , formatCmd , "[-year] [-state] list" , "lists all CVE IDs for an organization" )
68
66
flag .PrintDefaults ()
@@ -123,7 +121,7 @@ func main() {
123
121
if ! strings .HasSuffix (filename , ".json" ) && ! strings .HasSuffix (filename , ".yaml" ) {
124
122
logFatalUsageErr ("cve publish" , errors .New ("filename must end in '.json' or '.yaml'" ))
125
123
}
126
- if err := publish (c , filename , * publishUpdate ); err != nil {
124
+ if err := publish (c , filename ); err != nil {
127
125
log .Fatalf ("cve publish: could not publish CVE record due to error:\n %v" , err )
128
126
}
129
127
case "org" :
@@ -203,7 +201,7 @@ func validateID(id string) (string, error) {
203
201
return id , nil
204
202
}
205
203
206
- var stateRegex = regexp .MustCompile (`^(RESERVED|PUBLIC|REJECT )$` )
204
+ var stateRegex = regexp .MustCompile (`^(RESERVED|PUBLISHED|REJECTED )$` )
207
205
208
206
func validateState (state string ) (string , error ) {
209
207
if state != "" && ! stateRegex .MatchString (state ) {
@@ -254,10 +252,12 @@ func lookupID(c *cveclient.Client, id string) error {
254
252
return nil
255
253
}
256
254
257
- func recordToString (r * cveschema5.CVERecord ) string {
258
- s , err := json .MarshalIndent (r , "" , " " )
255
+ // toJSON converts a struct into a JSON string.
256
+ // If JSON marshal fails, it falls back to fmt.Sprint.
257
+ func toJSON (v any ) string {
258
+ s , err := json .Marshal (v )
259
259
if err != nil {
260
- s = [] byte ( fmt .Sprint (r ) )
260
+ return fmt .Sprint (v )
261
261
}
262
262
return string (s )
263
263
}
@@ -268,53 +268,67 @@ func lookupRecord(c *cveclient.Client, id string) error {
268
268
return err
269
269
}
270
270
// Display the retrieved CVE record.
271
- fmt .Println (recordToString (record ))
271
+ fmt .Println (toJSON (record ))
272
272
return nil
273
273
}
274
274
275
- func publish (c * cveclient.Client , filename string , update bool ) (err error ) {
276
- var toPublish * cveschema5.CVERecord
277
- switch {
278
- case strings .HasSuffix (filename , ".yaml" ):
279
- toPublish , err = report .ToCVE5 (filename )
275
+ func publish (c * cveclient.Client , filename string ) (err error ) {
276
+ if ! strings .HasSuffix (filename , ".json" ) {
277
+ return errors .New ("filename must end in '.json'" )
278
+ }
279
+
280
+ cveID , toPublish , err := cveschema5 .ReadForPublish (filename )
281
+ if err != nil {
282
+ return err
283
+ }
284
+
285
+ // Determine if the record should be created or updated.
286
+ assigned , err := c .RetrieveID (cveID )
287
+ if err != nil {
288
+ return err
289
+ }
290
+
291
+ var (
292
+ publish func (string , * cveschema5.Containers ) (* cveschema5.CVERecord , error )
293
+ action string
294
+ )
295
+ switch state := assigned .State ; state {
296
+ case cveschema5 .StatePublished :
297
+ existing , err := c .RetrieveRecord (cveID )
280
298
if err != nil {
281
299
return err
282
300
}
283
- case strings .HasSuffix (filename , ".json" ):
284
- toPublish , err = cveschema5 .Read (filename )
285
- if err != nil {
286
- return err
301
+ if diff := cmp .Diff (existing .Containers , * toPublish ); diff != "" {
302
+ fmt .Printf ("publish would update record for %s (-existing, +new):\n %s\n " , cveID , diff )
303
+ } else {
304
+ fmt .Println ("updating record would have no effect, exiting" )
305
+ return nil
287
306
}
307
+ publish = c .UpdateRecord
308
+ action = "update"
309
+ case cveschema5 .StateReserved :
310
+ fmt .Printf ("publish would create new record for %s\n " , cveID )
311
+ publish = c .CreateRecord
312
+ action = "create"
288
313
default :
289
- return errors . New ( "filename must end in '.json' or '.yaml'" )
314
+ return fmt . Errorf ( "publishing a %s record is not supported" , state )
290
315
}
291
316
292
317
reader := bufio .NewReader (os .Stdin )
293
- fmt .Printf ("ready to publish: \n %s \n continue ? (y/N)\n " , recordToString ( toPublish ) )
318
+ fmt .Printf ("%s record for %s ? (y/N)\n " , action , cveID )
294
319
text , _ := reader .ReadString ('\n' )
295
320
if text != "y\n " {
296
- fmt .Println ("exiting" )
321
+ fmt .Printf ("exiting without %sing record \n " , strings . TrimSuffix ( action , "e" ) )
297
322
return nil
298
323
}
299
324
300
- var (
301
- published * cveschema5.CVERecord
302
- action string
303
- )
304
- if update {
305
- published , err = c .UpdateRecord (toPublish .Metadata .ID , & toPublish .Containers )
306
- if err != nil {
307
- return err
308
- }
309
- action = "update"
310
- } else {
311
- published , err = c .CreateRecord (toPublish .Metadata .ID , & toPublish .Containers )
312
- if err != nil {
313
- return err
314
- }
315
- action = "create"
325
+ published , err := publish (cveID , toPublish )
326
+ if err != nil {
327
+ return err
316
328
}
317
- fmt .Printf ("successfully %sd record for %s:\n %v\n link: %s%s\n " , action , published .Metadata .ID , recordToString (published ), report .NISTPrefix , published .Metadata .ID )
329
+
330
+ fmt .Printf ("successfully %sd record for %s:\n \n %v\n \n link: %s%s\n " , action , cveID , toJSON (published ), report .MITREPrefix , cveID )
331
+
318
332
return nil
319
333
}
320
334
0 commit comments