Skip to content

Commit f7d4354

Browse files
committed
feat(fetch): add vulncheck nvd++
1 parent dd5130e commit f7d4354

File tree

7 files changed

+901
-17
lines changed

7 files changed

+901
-17
lines changed

commands/fetchvulncheck.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package commands
2+
3+
import (
4+
"errors"
5+
"time"
6+
7+
"github.com/spf13/cobra"
8+
"github.com/spf13/viper"
9+
"golang.org/x/xerrors"
10+
11+
"github.com/vulsio/go-cve-dictionary/db"
12+
"github.com/vulsio/go-cve-dictionary/fetcher/vulncheck"
13+
"github.com/vulsio/go-cve-dictionary/log"
14+
"github.com/vulsio/go-cve-dictionary/models"
15+
)
16+
17+
var fetchVulncheckCmd = &cobra.Command{
18+
Use: "vulncheck",
19+
Short: "Fetch Vulnerability dictionary from VulnCheck NVD++",
20+
Long: "Fetch Vulnerability dictionary from VulnCheck NVD++",
21+
RunE: fetchVulncheck,
22+
}
23+
24+
func init() {
25+
fetchCmd.AddCommand(fetchVulncheckCmd)
26+
}
27+
28+
func fetchVulncheck(_ *cobra.Command, args []string) (err error) {
29+
if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil {
30+
return xerrors.Errorf("Failed to SetLogger. err: %w", err)
31+
}
32+
33+
driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{})
34+
if err != nil {
35+
if errors.Is(err, db.ErrDBLocked) {
36+
return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err)
37+
}
38+
return xerrors.Errorf("Failed to open DB. err: %w", err)
39+
}
40+
41+
fetchMeta, err := driver.GetFetchMeta()
42+
if err != nil {
43+
return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err)
44+
}
45+
if fetchMeta.OutDated() {
46+
return xerrors.Errorf("Failed to Insert CVEs into DB. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion})
47+
}
48+
// If the fetch fails the first time (without SchemaVersion), the DB needs to be cleaned every time, so insert SchemaVersion.
49+
if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
50+
return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err)
51+
}
52+
53+
cves, err := vulncheck.FetchConvert()
54+
if err != nil {
55+
return xerrors.Errorf("Failed to fetch from VulnCheck NVD++. err: %w", err)
56+
}
57+
58+
log.Infof("Inserting VulnCheck NVD++ into DB (%s).", driver.Name())
59+
if err := driver.InsertVulncheck(cves); err != nil {
60+
return xerrors.Errorf("Failed to insert. dbpath: %s, err: %w", viper.GetString("dbpath"), err)
61+
}
62+
63+
fetchMeta.LastFetchedAt = time.Now()
64+
if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
65+
return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err)
66+
}
67+
68+
log.Infof("Finished fetching VulnCheck NVD++.")
69+
return nil
70+
}

db/db.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,14 @@ type DB interface {
4141
GetAdvisoriesCisco() (map[string][]string, error)
4242
InsertJvn([]string) error
4343
InsertNvd([]string) error
44+
InsertVulncheck(iter.Seq2[models.Vulncheck, error]) error
4445
InsertFortinet([]models.Fortinet) error
4546
InsertMitre([]string) error
4647
InsertPaloalto(iter.Seq2[models.Paloalto, error]) error
4748
InsertCisco(iter.Seq2[models.Cisco, error]) error
48-
CountNvd() (int, error)
4949
CountJvn() (int, error)
50+
CountNvd() (int, error)
51+
CountVulncheck() (int, error)
5052
CountFortinet() (int, error)
5153
CountMitre() (int, error)
5254
CountPaloalto() (int, error)

db/rdb.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,16 @@ func (r *RDBDriver) MigrateDB() error {
160160
&models.NvdReference{},
161161
&models.NvdCert{},
162162

163+
&models.Vulncheck{},
164+
&models.VulncheckDescription{},
165+
&models.VulncheckCvss2Extra{},
166+
&models.VulncheckCvss3{},
167+
&models.VulncheckCvss40{},
168+
&models.VulncheckCwe{},
169+
&models.VulncheckCpe{},
170+
&models.VulncheckReference{},
171+
&models.VulncheckCert{},
172+
163173
&models.Jvn{},
164174
&models.JvnCvss2{},
165175
&models.JvnCvss3{},
@@ -894,6 +904,85 @@ func insertNvd(tx *gorm.DB, cves []models.Nvd, batchSize int) error {
894904
return nil
895905
}
896906

907+
// CountVulncheck count vulncheck table
908+
func (r *RDBDriver) CountVulncheck() (int, error) {
909+
var count int64
910+
if err := r.conn.Model(&models.Vulncheck{}).Count(&count).Error; err != nil {
911+
return 0, err
912+
}
913+
return int(count), nil
914+
}
915+
916+
// InsertVulncheck Cve information from DB.
917+
func (r *RDBDriver) InsertVulncheck(cves iter.Seq2[models.Vulncheck, error]) (err error) {
918+
bar := pb.ProgressBarTemplate(`{{cycle . "[ ]" "[=> ]" "[===> ]" "[=====> ]" "[======> ]" "[========> ]" "[==========> ]" "[============> ]" "[==============> ]" "[================> ]" "[==================> ]" "[===================>]"}} {{counters .}} files processed. ({{speed .}})`).New(0).Start().SetWriter(func() io.Writer {
919+
if viper.GetBool("log-json") {
920+
return io.Discard
921+
}
922+
return os.Stderr
923+
}())
924+
925+
tx := r.conn.Begin()
926+
defer func() {
927+
if re := recover(); re != nil {
928+
tx.Rollback()
929+
}
930+
}()
931+
if err := tx.Error; err != nil {
932+
return err
933+
}
934+
935+
batchSize := viper.GetInt("batch-size")
936+
if batchSize < 1 {
937+
return fmt.Errorf("Failed to set batch-size. err: batch-size option is not set properly")
938+
}
939+
940+
logger.Infof("Deleting Vulncheck tables...")
941+
if err := deleteVulncheck(tx); err != nil {
942+
tx.Rollback()
943+
return xerrors.Errorf("Failed to deleteVulncheck. err: %w", err)
944+
}
945+
946+
for cve, err := range cves {
947+
if err != nil {
948+
tx.Rollback()
949+
return xerrors.Errorf("Failed to convert Vulncheck cves. err: %w", err)
950+
}
951+
952+
if err := tx.Omit("Cpes").Create(&cve).Error; err != nil {
953+
tx.Rollback()
954+
return xerrors.Errorf("Failed to insert. err: %w", err)
955+
}
956+
957+
for i := range cve.Cpes {
958+
cve.Cpes[i].VulncheckID = uint(cve.ID)
959+
}
960+
961+
for chunk := range slices.Chunk(cve.Cpes, batchSize) {
962+
if err := tx.Create(chunk).Error; err != nil {
963+
tx.Rollback()
964+
return xerrors.Errorf("Failed to insert. err: %w", err)
965+
}
966+
}
967+
968+
bar.Increment()
969+
}
970+
971+
if err := tx.Commit().Error; err != nil {
972+
return xerrors.Errorf("Failed to Commit Transaction. err: %w", err)
973+
}
974+
return nil
975+
}
976+
977+
func deleteVulncheck(tx *gorm.DB) error {
978+
for _, table := range []interface{}{models.Vulncheck{}, models.VulncheckDescription{}, models.VulncheckCvss2Extra{}, models.VulncheckCvss3{}, models.VulncheckCvss40{}, models.VulncheckCwe{}, models.VulncheckCpe{}, models.VulncheckReference{}, models.VulncheckCert{}} {
979+
if err := tx.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(table).Error; err != nil {
980+
return xerrors.Errorf("Failed to delete old records. err: %w", err)
981+
}
982+
}
983+
return nil
984+
}
985+
897986
// GetAdvisoriesFortinet get AdvisoryID: []CVE IDs
898987
func (r *RDBDriver) GetAdvisoriesFortinet() (map[string][]string, error) {
899988
var fs []models.Fortinet

0 commit comments

Comments
 (0)