vulndb/export.go (192 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"
"strconv"
"time"
"github.com/pkg/errors"
"github.com/facebookincubator/flog"
"github.com/facebookincubator/nvdtools/vulndb/debug"
"github.com/facebookincubator/nvdtools/vulndb/sqlutil"
)
// DataExporter is a helper for exporting vulnerability records from the db.
type DataExporter struct {
DB *sql.DB
FilterProviders []string
FilterCVEs []string
}
func (exp DataExporter) selectLatestVersion() *sqlutil.SelectStmt {
q := sqlutil.Select(
"latest.version",
).From().
SelectGroup("latest", latestVendorVersion())
if len(exp.FilterProviders) > 0 {
q = q.Where(
sqlutil.Cond().In("provider", exp.FilterProviders),
)
}
return q
}
// fields must be vendor.field_name or vendor_data.field_name
func (exp DataExporter) selectVendorData(fields ...string) *sqlutil.SelectStmt {
q := sqlutil.Select(
fields...,
).From(
"vendor_data",
).Literal(
"LEFT JOIN vendor ON vendor.version = vendor_data.version",
).Literal(
"LEFT JOIN custom_data ON custom_data.cve_id = vendor_data.cve_id",
)
cond := sqlutil.Cond().
InSelect("vendor.version", exp.selectLatestVersion()).
And().
IsNull("custom_data.cve_id")
if len(exp.FilterCVEs) > 0 {
cond = cond.And().In("vendor_data.cve_id", exp.FilterCVEs)
}
q = q.Where(cond)
return q
}
func (exp DataExporter) selectOverrides(fields ...string) *sqlutil.SelectStmt {
q := sqlutil.Select(
fields...,
).From(
"custom_data",
)
var cond *sqlutil.QueryConditionSet
if len(exp.FilterProviders) > 0 {
cond = sqlutil.Cond().In("provider", exp.FilterProviders)
}
if len(exp.FilterCVEs) > 0 {
if cond == nil {
cond = sqlutil.Cond()
} else {
cond = cond.And()
}
cond = cond.In("cve_id", exp.FilterCVEs)
}
if cond != nil {
q = q.Where(cond)
}
return q
}
// CSV exports data to w.
func (exp DataExporter) CSV(ctx context.Context, w io.Writer, header bool) error {
q := sqlutil.Select(
"d.owner",
"d.provider",
"d.cve_id",
"d.published",
"d.modified",
"d.base_score",
"d.summary",
).From().SelectGroup(
"d",
exp.selectVendorData(
"vendor.owner",
"vendor.provider",
"vendor_data.cve_id",
"vendor_data.published",
"vendor_data.modified",
"vendor_data.base_score",
"vendor_data.summary",
).Literal("UNION ALL").
Select(exp.selectOverrides(
"custom_data.owner",
"custom_data.provider",
"custom_data.cve_id",
"custom_data.published",
"custom_data.modified",
"custom_data.base_score",
"custom_data.summary",
)),
)
query, args := q.String(), q.QueryArgs()
if debug.V(1) {
flog.Infof("running: %q / %#v", query, args)
}
rows, err := exp.DB.QueryContext(ctx, query, args...)
if err != nil {
return errors.Wrap(err, "cannot query export data")
}
defer rows.Close()
record := 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"`
}{}
cw := csv.NewWriter(w)
defer cw.Flush()
if header {
fields := sqlutil.NewRecordType(record).Fields()
cw.Write(fields)
}
for rows.Next() {
v := record
err = rows.Scan(sqlutil.NewRecordType(&v).Values()...)
if err != nil {
return errors.Wrap(err, "cannot scan export 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 exports NVD CVE JSON to w.
func (exp DataExporter) JSON(ctx context.Context, w io.Writer, indent string) error {
q := sqlutil.Select(
"d.cve_id",
"d.cve_json",
).From().SelectGroup(
"d",
exp.selectVendorData(
"vendor_data.cve_id AS cve_id",
"vendor_data.cve_json AS cve_json",
).
Literal("UNION ALL").
Select(exp.selectOverrides(
"custom_data.cve_id",
"custom_data.cve_json",
)),
)
query, args := q.String(), q.QueryArgs()
if debug.V(1) {
flog.Infof("running: %q / %#v", query, args)
}
rows, err := exp.DB.QueryContext(ctx, query, args...)
if err != nil {
return errors.Wrap(err, "cannot query export 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 export 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)
}