vulndb/custom.go (204 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package vulndb import ( "context" "database/sql" "encoding/csv" "io" "os" "strconv" "time" "github.com/pkg/errors" "github.com/facebookincubator/flog" "github.com/facebookincubator/nvdtools/vulndb/debug" "github.com/facebookincubator/nvdtools/vulndb/sqlutil" ) // CustomDataRecord represents a db record of the `custom_data` table. type CustomDataRecord struct { Owner string `sql:"owner"` Provider string `sql:"provider"` CVE string `sql:"cve_id"` Published time.Time `sql:"published"` Modified time.Time `sql:"modified"` BaseScore float64 `sql:"base_score"` Summary string `sql:"summary"` JSON []byte `sql:"cve_json"` } // CustomDataImporter is a helper for importing custom data. type CustomDataImporter struct { DB *sql.DB Owner string Provider string } // ImportJSON imports NVD CVE JSON 1.0 optionally gzipped. func (o CustomDataImporter) ImportJSON(ctx context.Context, r io.Reader) error { records, err := o.recordsFromJSON(r) if err != nil { return err } return o.importData(ctx, records) } // ImportFile imports NVD CVE JSON 1.0 optionally gzipped from file. func (o CustomDataImporter) ImportFile(ctx context.Context, name string) error { f, err := os.Open(name) if err != nil { return errors.Wrap(err, "cannot open custom data file") } defer f.Close() return o.ImportJSON(ctx, f) } func (o CustomDataImporter) recordsFromJSON(r io.Reader) ([]CustomDataRecord, error) { feed, err := parseNVDCVEJSON(r) if err != nil { return nil, errors.Wrap(err, "cannot parse custom data records") } records := make([]CustomDataRecord, len(feed.CVEItems)) for i, item := range feed.CVEItems { cve := cveItem{item} records[i] = CustomDataRecord{ Owner: o.Owner, Provider: o.Provider, CVE: cve.ID(), Published: cve.Published(), Modified: cve.Modified(), BaseScore: cve.BaseScore(), Summary: cve.Summary(), JSON: cve.JSON(), } } return records, nil } func (o CustomDataImporter) importData(ctx context.Context, data []CustomDataRecord) error { r := sqlutil.NewRecords(data) q := sqlutil.Replace(). Into("custom_data"). Fields(r.Fields()...). Values(r...) query, args := q.String(), q.QueryArgs() if debug.V(2) { flog.Infof("running: %q", query) } _, err := o.DB.ExecContext(ctx, query, args...) if err != nil { return errors.Wrap(err, "cannot insert custom data records") } return nil } // CustomDataExporter is a helper for exporting custom data. type CustomDataExporter struct { DB *sql.DB Provider string FilterCVEs []string } func (o CustomDataExporter) condition() *sqlutil.QueryConditionSet { cond := sqlutil.Cond().Equal("provider", o.Provider) if len(o.FilterCVEs) > 0 { cond = cond.And().In("cve_id", o.FilterCVEs) } return cond } // CSV writes custom data records to w. func (o CustomDataExporter) CSV(ctx context.Context, w io.Writer, header bool) error { fields := []string{ "owner", "provider", "cve_id", "published", "modified", "base_score", "summary", } q := sqlutil.Select( fields..., ).From( "custom_data", ).Where( o.condition(), ) query, args := q.String(), q.QueryArgs() if debug.V(1) { flog.Infof("running: %q / %#v", query, args) } rows, err := o.DB.QueryContext(ctx, query, args...) if err != nil { return errors.Wrap(err, "cannot query vendor data") } defer rows.Close() cw := csv.NewWriter(w) defer cw.Flush() if header { cw.Write(fields) } for rows.Next() { var v CustomDataRecord r := sqlutil.NewRecordType(&v).Subset(fields...) err = rows.Scan(r.Values()...) if err != nil { return errors.Wrap(err, "cannot scan custom data") } cw.Write([]string{ v.Owner, v.Provider, v.CVE, v.Published.Format(TimeLayout), v.Modified.Format(TimeLayout), strconv.FormatFloat(v.BaseScore, 'f', 3, 64), v.Summary, }) } return nil } // JSON writes NVD CVE JSON to w. func (o CustomDataExporter) JSON(ctx context.Context, w io.Writer, indent string) error { q := sqlutil.Select( "cve_id", "cve_json", ).From( "custom_data", ).Where( o.condition(), ) query, args := q.String(), q.QueryArgs() if debug.V(1) { flog.Infof("running: %q / %#v", query, args) } rows, err := o.DB.QueryContext(ctx, query, args...) if err != nil { return errors.Wrap(err, "cannot query vendor data") } defer rows.Close() record := struct { CVE string JSON []byte }{} f := &cveFile{} for rows.Next() { v := record err = rows.Scan(sqlutil.NewRecordType(&v).Values()...) if err != nil { return errors.Wrap(err, "cannot scan vendor data") } err = f.Add(v.CVE, v.JSON) if err != nil { return err } } if indent == "" { return f.EncodeJSON(w) } const prefix = "" return f.EncodeIndentedJSON(w, prefix, indent) } // CustomDataDeleter is a helper for deleting custom data. type CustomDataDeleter struct { DB *sql.DB Provider string FilterCVEs []string } // Delete deletes custom data from the database. func (o CustomDataDeleter) Delete(ctx context.Context) error { cond := sqlutil.Cond().Equal("provider", o.Provider) if len(o.FilterCVEs) > 0 { cond = cond.And().In("cve_id", o.FilterCVEs) } q := sqlutil.Delete().From("custom_data").Where(cond) query, args := q.String(), q.QueryArgs() if debug.V(1) { flog.Infof("running: %q / %#v", query, args) } _, err := o.DB.ExecContext(ctx, query, args...) if err != nil { return errors.Wrap(err, "cannot delete custom data") } return nil }