Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
}
},
{
"name": "Export Diffs",
"name": "VulnDB Export Diffs",
"type": "go",
"request": "launch",
"cwd": "${workspaceRoot}",
Expand All @@ -62,6 +62,30 @@
"vulndb",
"export",
],
},
{
"name": "VulnDB Sync",
"type": "go",
"request": "launch",
"cwd": "${workspaceRoot}",
"mode": "auto",
"program": "${workspaceRoot}/cmd/devguard-cli/main.go",
"args": [
"vulndb",
"sync",
]
},
{
"name": "VulnDB Import",
"type": "go",
"request": "launch",
"cwd": "${workspaceRoot}",
"mode": "auto",
"program": "${workspaceRoot}/cmd/devguard-cli/main.go",
"args": [
"vulndb",
"import",
]
}
]
}
193 changes: 66 additions & 127 deletions cmd/devguard-cli/commands/vulndb.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
package commands

import (
"context"
"errors"
"log/slog"
"os"
"regexp"
"slices"
"strings"
"time"

"github.com/l3montree-dev/devguard/accesscontrol"
"github.com/l3montree-dev/devguard/cmd/devguard/api"
"github.com/l3montree-dev/devguard/controllers"
"github.com/l3montree-dev/devguard/daemons"
"github.com/l3montree-dev/devguard/database"
"github.com/l3montree-dev/devguard/database/repositories"
"github.com/l3montree-dev/devguard/integrations"
"github.com/l3montree-dev/devguard/router"
"github.com/l3montree-dev/devguard/services"
"github.com/l3montree-dev/devguard/shared"
"github.com/l3montree-dev/devguard/utils"
"github.com/l3montree-dev/devguard/vulndb"
"github.com/spf13/cobra"
"go.uber.org/fx"
)

func NewVulndbCommand() *cobra.Command {
Expand All @@ -24,7 +30,6 @@ func NewVulndbCommand() *cobra.Command {
Short: "Vulnerability Database",
}

vulndbCmd.AddCommand(newImportCVECommand())
vulndbCmd.AddCommand(newSyncCommand())
vulndbCmd.AddCommand(newImportCommand())
vulndbCmd.AddCommand(newExportIncrementalCommand())
Expand All @@ -40,21 +45,6 @@ func emptyOrContains(s []string, e string) bool {
return slices.Contains(s, e)
}

func isValidCVE(cveID string) bool {
// should either be just 2023-1234 or cve-2023-1234
if len(cveID) == 0 {
return false
}

r := regexp.MustCompile(`^CVE-\d{4}-\d{4,7}$`)
if r.MatchString(cveID) {
return true
}

r = regexp.MustCompile(`^\d{4}-\d{4,7}$`)
return r.MatchString(cveID)
}

func migrateDB(db shared.DB) {
// Run database migrations using the existing database connection
disableAutoMigrate := os.Getenv("DISABLE_AUTOMIGRATE")
Expand All @@ -65,11 +55,53 @@ func migrateDB(db shared.DB) {
panic(errors.New("Failed to run database migrations"))
}

// Run hash migrations if needed (when algorithm version changes)
if err := vulndb.RunHashMigrationsIfNeeded(db); err != nil {
slog.Error("failed to run hash migrations", "error", err)
panic(errors.New("Failed to run hash migrations"))
}
var daemonRunner shared.DaemonRunner

fx.New(
// fx.NopLogger,
fx.Supply(db),
fx.Provide(database.BrokerFactory),
fx.Provide(api.NewServer),
repositories.Module,
controllers.ControllerModule,
services.ServiceModule,
router.RouterModule,
accesscontrol.AccessControlModule,
integrations.Module,
daemons.Module,

// we need to invoke all routers to register their routes
fx.Invoke(func(OrgRouter router.OrgRouter) {}),
fx.Invoke(func(ProjectRouter router.ProjectRouter) {}),
fx.Invoke(func(SessionRouter router.SessionRouter) {}),
fx.Invoke(func(ArtifactRouter router.ArtifactRouter) {}),
fx.Invoke(func(AssetRouter router.AssetRouter) {}),
fx.Invoke(func(AssetVersionRouter router.AssetVersionRouter) {}),
fx.Invoke(func(DependencyVulnRouter router.DependencyVulnRouter) {}),
fx.Invoke(func(FirstPartyVulnRouter router.FirstPartyVulnRouter) {}),
fx.Invoke(func(LicenseRiskRouter router.LicenseRiskRouter) {}),
fx.Invoke(func(ShareRouter router.ShareRouter) {}),
fx.Invoke(func(VulnDBRouter router.VulnDBRouter) {}),
fx.Invoke(func(dependencyProxyRouter router.DependencyProxyRouter) {}),
fx.Invoke(func(lc fx.Lifecycle, server api.Server) {
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
go server.Start() // start in background
return nil
},
})
}),
fx.Invoke(func(lc fx.Lifecycle, daemonRunner shared.DaemonRunner) {
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
go daemonRunner.Start() // start in background
return nil
},
})
}),
fx.Populate(&daemonRunner),
)

} else {
slog.Info("automatic migrations disabled via DISABLE_AUTOMIGRATE=true")
}
Expand Down Expand Up @@ -97,54 +129,6 @@ func newCleanupCommand() *cobra.Command {
return cleanupCmd
}

func newImportCVECommand() *cobra.Command {
importCmd := &cobra.Command{
Use: "import-cve",
Short: "Will import the vulnerability database",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
shared.LoadConfig() // nolint
db, err := shared.DatabaseFactory()
if err != nil {
slog.Error("could not connect to database", "err", err)
return
}

migrateDB(db)

cveID := args[0]
cveID = strings.TrimSpace(strings.ToUpper(cveID))
// check if first argument is valid cve
if !isValidCVE(cveID) {
slog.Error("invalid cve id", "cve", cveID)
return
}

cveRepository := repositories.NewCVERepository(db)
nvdService := vulndb.NewNVDService(cveRepository)
osvService := vulndb.NewOSVService(repositories.NewAffectedComponentRepository(db))

cve, err := nvdService.ImportCVE(cveID)

if err != nil {
slog.Error("could not import cve", "err", err)
return
}

// the osv database provides additional information about affected packages
affectedPackages, err := osvService.ImportCVE(cveID)
if err != nil {
slog.Error("could not import cve from osv", "err", err)
return
}

slog.Info("successfully imported affected packages", "cveID", cve.CVE, "affectedPackages", len(affectedPackages))
},
}

return importCmd
}

func newImportCommand() *cobra.Command {
importCmd := &cobra.Command{
Use: "import",
Expand All @@ -165,9 +149,6 @@ func newImportCommand() *cobra.Command {
affectedComponentsRepository := repositories.NewAffectedComponentRepository(database)
configService := services.NewConfigService(database)
v := vulndb.NewImportService(cveRepository, cweRepository, exploitsRepository, affectedComponentsRepository, configService)
for _, arg := range args {
slog.Info(arg)
}

err = v.ImportFromDiff(nil)
if err != nil {
Expand All @@ -185,10 +166,6 @@ func newSyncCommand() *cobra.Command {
Short: "Will sync the vulnerability database",
Args: cobra.ExactArgs(0),
Run: func(cmd *cobra.Command, args []string) {
// check if after flag is set
after, _ := cmd.Flags().GetString("after")
startIndex, _ := cmd.Flags().GetInt("startIndex")

shared.LoadConfig() // nolint

db, err := shared.DatabaseFactory()
Expand All @@ -203,15 +180,17 @@ func newSyncCommand() *cobra.Command {

cveRepository := repositories.NewCVERepository(db)
cweRepository := repositories.NewCWERepository(db)
cveRelationshipRepository := repositories.NewCveRelationshipRepository(db)
affectedCmpRepository := repositories.NewAffectedComponentRepository(db)
nvdService := vulndb.NewNVDService(cveRepository)

mitreService := vulndb.NewMitreService(cweRepository)
epssService := vulndb.NewEPSSService(nvdService, cveRepository)
osvService := vulndb.NewOSVService(affectedCmpRepository)
epssService := vulndb.NewEPSSService(cveRepository)
osvService := vulndb.NewOSVService(affectedCmpRepository, cveRepository, cveRelationshipRepository)

// cvelistService := vulndb.NewCVEListService(cveRepository)
debianSecurityTracker := vulndb.NewDebianSecurityTracker(affectedCmpRepository)

expoitDBService := vulndb.NewExploitDBService(nvdService, repositories.NewExploitRepository(db))
expoitDBService := vulndb.NewExploitDBService(repositories.NewExploitRepository(db))

githubExploitDBService := vulndb.NewGithubExploitDBService(repositories.NewExploitRepository(db))

Expand All @@ -224,46 +203,15 @@ func newSyncCommand() *cobra.Command {
slog.Info("finished cwe database sync", "duration", time.Since(now))
}

if emptyOrContains(databasesToSync, "nvd") {
slog.Info("starting nvd database sync")
if emptyOrContains(databasesToSync, "osv") {
slog.Info("starting osv database sync")
now := time.Now()
if after != "" {
// we do a partial sync
// try to parse the date
afterDate, err := time.Parse("2006-01-02", after)
if err != nil {
slog.Error("could not parse after date", "err", err, "provided", after, "expectedFormat", "2006-01-02")
}
err = nvdService.FetchAfter(afterDate)
if err != nil {
slog.Error("could not fetch after date", "err", err)
}
} else {
if startIndex != 0 {
err = nvdService.FetchAfterIndex(startIndex)
if err != nil {
slog.Error("could not fetch after index", "err", err)
}
} else {
err = nvdService.Sync()
if err != nil {
slog.Error("could not do initial sync", "err", err)
}
}
if err := osvService.Mirror(); err != nil {
slog.Error("could not sync osv database", "err", err)
}
slog.Info("finished nvd database sync", "duration", time.Since(now))
slog.Info("finished osv database sync", "duration", time.Since(now))
}

/*if emptyOrContains(databasesToSync, "cvelist") {
slog.Info("starting cvelist database sync")
now := time.Now()

if err := cvelistService.Mirror(); err != nil {
slog.Error("could not mirror cvelist database", "err", err)
}
slog.Info("finished cvelist database sync", "duration", time.Since(now))
}*/

if emptyOrContains(databasesToSync, "epss") {
slog.Info("starting epss database sync")
now := time.Now()
Expand All @@ -274,15 +222,6 @@ func newSyncCommand() *cobra.Command {
slog.Info("finished epss database sync", "duration", time.Since(now))
}

if emptyOrContains(databasesToSync, "osv") {
slog.Info("starting osv database sync")
now := time.Now()
if err := osvService.Mirror(); err != nil {
slog.Error("could not sync osv database", "err", err)
}
slog.Info("finished osv database sync", "duration", time.Since(now))
}

if emptyOrContains(databasesToSync, "exploitdb") {
slog.Info("starting exploitdb database sync")
now := time.Now()
Expand Down
Loading
Loading