зеркало из https://github.com/golang/vulndb.git
cmd/cve, internal/cveclient: check if a record is already published in cve publish
Instead of asking the user to provide a flag -update indicating whether a record is already published, use the CVE Services API to determine this automatically. Change-Id: I6e5bf7d6e095360335043424eb3330aeaf23b297 Reviewed-on: https://go-review.googlesource.com/c/vulndb/+/446218 Reviewed-by: Jonathan Amsterdam <jba@google.com> Reviewed-by: Tatiana Bradley <tatiana@golang.org> TryBot-Result: Gopher Robot <gobot@golang.org> Run-TryBot: Tatiana Bradley <tatiana@golang.org>
This commit is contained in:
Родитель
4b2e40139f
Коммит
bbf7cc70a7
|
@ -18,6 +18,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"golang.org/x/vulndb/internal/cveclient"
|
||||
"golang.org/x/vulndb/internal/cveschema5"
|
||||
"golang.org/x/vulndb/internal/report"
|
||||
|
@ -43,10 +44,7 @@ var (
|
|||
reserveSequential = flag.Bool("seq", true, "reserve: if true, reserve new CVE ID batches in sequence")
|
||||
|
||||
// flags for the list command
|
||||
listState = flag.String("state", "", "list: filter by CVE state (RESERVED, PUBLIC, or REJECT)")
|
||||
|
||||
// flags for the publish command
|
||||
publishUpdate = flag.Bool("update", false, "publish: if true, update an existing CVE Record")
|
||||
listState = flag.String("state", "", "list: filter by CVE state (RESERVED, PUBLISHED, or REJECTED)")
|
||||
|
||||
// flags that apply to multiple commands
|
||||
year = flag.Int("year", 0, "reserve: the CVE ID year for newly reserved CVE IDs (default is current year)\nlist: filter by the year in the CVE ID")
|
||||
|
@ -62,7 +60,7 @@ func main() {
|
|||
fmt.Fprintf(out, formatCmd, "quota", "outputs the CVE ID quota of the authenticated organization")
|
||||
fmt.Fprintf(out, formatCmd, "id {cve-id}", "outputs details on an assigned CVE ID (CVE-YYYY-NNNN)")
|
||||
fmt.Fprintf(out, formatCmd, "record {cve-id}", "outputs the record associated with a CVE ID (CVE-YYYY-NNNN)")
|
||||
fmt.Fprintf(out, formatCmd, "[-update] publish {filename}", "publishes a CVE Record from a YAML or JSON file")
|
||||
fmt.Fprintf(out, formatCmd, "publish {filename}", "publishes or updates a CVE Record from a YAML or JSON file")
|
||||
fmt.Fprintf(out, formatCmd, "org", "outputs details on the authenticated organization")
|
||||
fmt.Fprintf(out, formatCmd, "[-year] [-state] list", "lists all CVE IDs for an organization")
|
||||
flag.PrintDefaults()
|
||||
|
@ -123,7 +121,7 @@ func main() {
|
|||
if !strings.HasSuffix(filename, ".json") && !strings.HasSuffix(filename, ".yaml") {
|
||||
logFatalUsageErr("cve publish", errors.New("filename must end in '.json' or '.yaml'"))
|
||||
}
|
||||
if err := publish(c, filename, *publishUpdate); err != nil {
|
||||
if err := publish(c, filename); err != nil {
|
||||
log.Fatalf("cve publish: could not publish CVE record due to error:\n %v", err)
|
||||
}
|
||||
case "org":
|
||||
|
@ -203,7 +201,7 @@ func validateID(id string) (string, error) {
|
|||
return id, nil
|
||||
}
|
||||
|
||||
var stateRegex = regexp.MustCompile(`^(RESERVED|PUBLIC|REJECT)$`)
|
||||
var stateRegex = regexp.MustCompile(`^(RESERVED|PUBLISHED|REJECTED)$`)
|
||||
|
||||
func validateState(state string) (string, error) {
|
||||
if state != "" && !stateRegex.MatchString(state) {
|
||||
|
@ -254,10 +252,12 @@ func lookupID(c *cveclient.Client, id string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func recordToString(r *cveschema5.CVERecord) string {
|
||||
s, err := json.MarshalIndent(r, "", " ")
|
||||
// toJSON converts a struct into a JSON string.
|
||||
// If JSON marshal fails, it falls back to fmt.Sprint.
|
||||
func toJSON(v any) string {
|
||||
s, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
s = []byte(fmt.Sprint(r))
|
||||
return fmt.Sprint(v)
|
||||
}
|
||||
return string(s)
|
||||
}
|
||||
|
@ -268,53 +268,67 @@ func lookupRecord(c *cveclient.Client, id string) error {
|
|||
return err
|
||||
}
|
||||
// Display the retrieved CVE record.
|
||||
fmt.Println(recordToString(record))
|
||||
fmt.Println(toJSON(record))
|
||||
return nil
|
||||
}
|
||||
|
||||
func publish(c *cveclient.Client, filename string, update bool) (err error) {
|
||||
var toPublish *cveschema5.CVERecord
|
||||
switch {
|
||||
case strings.HasSuffix(filename, ".yaml"):
|
||||
toPublish, err = report.ToCVE5(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case strings.HasSuffix(filename, ".json"):
|
||||
toPublish, err = cveschema5.Read(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return errors.New("filename must end in '.json' or '.yaml'")
|
||||
func publish(c *cveclient.Client, filename string) (err error) {
|
||||
if !strings.HasSuffix(filename, ".json") {
|
||||
return errors.New("filename must end in '.json'")
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
fmt.Printf("ready to publish:\n%s\ncontinue? (y/N)\n", recordToString(toPublish))
|
||||
text, _ := reader.ReadString('\n')
|
||||
if text != "y\n" {
|
||||
fmt.Println("exiting")
|
||||
return nil
|
||||
cveID, toPublish, err := cveschema5.ReadForPublish(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Determine if the record should be created or updated.
|
||||
assigned, err := c.RetrieveID(cveID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
published *cveschema5.CVERecord
|
||||
action string
|
||||
publish func(string, *cveschema5.Containers) (*cveschema5.CVERecord, error)
|
||||
action string
|
||||
)
|
||||
if update {
|
||||
published, err = c.UpdateRecord(toPublish.Metadata.ID, &toPublish.Containers)
|
||||
switch state := assigned.State; state {
|
||||
case cveschema5.StatePublished:
|
||||
existing, err := c.RetrieveRecord(cveID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if diff := cmp.Diff(existing.Containers, *toPublish); diff != "" {
|
||||
fmt.Printf("publish would update record for %s (-existing, +new):\n%s\n", cveID, diff)
|
||||
} else {
|
||||
fmt.Println("updating record would have no effect, exiting")
|
||||
return nil
|
||||
}
|
||||
publish = c.UpdateRecord
|
||||
action = "update"
|
||||
} else {
|
||||
published, err = c.CreateRecord(toPublish.Metadata.ID, &toPublish.Containers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case cveschema5.StateReserved:
|
||||
fmt.Printf("publish would create new record for %s\n", cveID)
|
||||
publish = c.CreateRecord
|
||||
action = "create"
|
||||
default:
|
||||
return fmt.Errorf("publishing a %s record is not supported", state)
|
||||
}
|
||||
fmt.Printf("successfully %sd record for %s:\n%v\nlink: %s%s\n", action, published.Metadata.ID, recordToString(published), report.NISTPrefix, published.Metadata.ID)
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
fmt.Printf("%s record for %s? (y/N)\n", action, cveID)
|
||||
text, _ := reader.ReadString('\n')
|
||||
if text != "y\n" {
|
||||
fmt.Printf("exiting without %sing record\n", strings.TrimSuffix(action, "e"))
|
||||
return nil
|
||||
}
|
||||
|
||||
published, err := publish(cveID, toPublish)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("successfully %sd record for %s:\n\n%v\n\nlink: %s%s\n", action, cveID, toJSON(published), report.MITREPrefix, cveID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -55,12 +55,12 @@ func New(cfg Config) *Client {
|
|||
|
||||
// AssignedCVE contains information about an assigned CVE.
|
||||
type AssignedCVE struct {
|
||||
ID string `json:"cve_id"`
|
||||
Year string `json:"cve_year"`
|
||||
State string `json:"state"`
|
||||
CNA string `json:"owning_cna"`
|
||||
Reserved time.Time `json:"reserved"`
|
||||
RequestedBy RequestedBy `json:"requested_by"`
|
||||
ID string `json:"cve_id"`
|
||||
Year string `json:"cve_year"`
|
||||
State cveschema5.State `json:"state"`
|
||||
CNA string `json:"owning_cna"`
|
||||
Reserved time.Time `json:"reserved"`
|
||||
RequestedBy RequestedBy `json:"requested_by"`
|
||||
}
|
||||
|
||||
// RequestedBy indicates the requesting user and organization for a CVE.
|
||||
|
|
|
@ -65,7 +65,7 @@ var (
|
|||
testTime1992 = time.Date(1992, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
)
|
||||
|
||||
func newTestCVE(id, state, year string) AssignedCVE {
|
||||
func newTestCVE(id string, state cveschema5.State, year string) AssignedCVE {
|
||||
return AssignedCVE{
|
||||
ID: id,
|
||||
Year: year,
|
||||
|
|
|
@ -122,3 +122,13 @@ func Read(filename string) (*CVERecord, error) {
|
|||
}
|
||||
return &record, nil
|
||||
}
|
||||
|
||||
// ReadForPublish reads the portion of a CVE record that can be published
|
||||
// via the CVE Services API from filename.
|
||||
func ReadForPublish(filename string) (cveID string, toPublish *Containers, err error) {
|
||||
record, err := Read(filename)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return record.Metadata.ID, &record.Containers, nil
|
||||
}
|
||||
|
|
|
@ -214,7 +214,7 @@ func (r *Report) GetAliases() []string {
|
|||
|
||||
const (
|
||||
NISTPrefix = "https://nvd.nist.gov/vuln/detail/"
|
||||
mitrePrefix = "https://cve.mitre.org/cgi-bin/cvename.cgi?name="
|
||||
MITREPrefix = "https://cve.org/CVERecord?id="
|
||||
ghsaURLPrefix = "https://github.com/advisories/"
|
||||
goURLPrefix = "https://pkg.go.dev/vuln/"
|
||||
)
|
||||
|
|
Загрузка…
Ссылка в новой задаче