Skip to content

feat(detector): use vuls2 for RedHat, CentOS, Alma and Rocky #2106

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from 10 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
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type Config struct {
Metasploit MetasploitConf `json:"metasploit,omitempty"`
KEVuln KEVulnConf `json:"kevuln,omitempty"`
Cti CtiConf `json:"cti,omitempty"`
Vuls2 Vuls2DictConf `json:"vuls2Dict,omitempty"`

Slack SlackConf `json:"-"`
EMail SMTPConf `json:"-"`
Expand Down
6 changes: 6 additions & 0 deletions config/vulnDictConf.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,3 +328,9 @@ func (cnf *CtiConf) Init() {
cnf.setDefault("go-cti.sqlite3")
cnf.DebugSQL = Conf.DebugSQL
}

type Vuls2DictConf struct {
Repository string
Path string
SkipUpdate bool
}
13 changes: 11 additions & 2 deletions detector/detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"github.com/future-architect/vuls/constant"
"github.com/future-architect/vuls/contrib/owasp-dependency-check/parser"
"github.com/future-architect/vuls/cwe"
"github.com/future-architect/vuls/detector/vuls2"
"github.com/future-architect/vuls/gost"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
Expand Down Expand Up @@ -49,7 +50,7 @@
return nil, xerrors.Errorf("Failed to fill with Library dependency: %w", err)
}

if err := DetectPkgCves(&r, config.Conf.OvalDict, config.Conf.Gost, config.Conf.LogOpts); err != nil {
if err := DetectPkgCves(&r, config.Conf.OvalDict, config.Conf.Gost, config.Conf.Vuls2, config.Conf.LogOpts, config.Conf.NoProgress); err != nil {
return nil, xerrors.Errorf("Failed to detect Pkg CVE: %w", err)
}

Expand Down Expand Up @@ -317,14 +318,19 @@

// DetectPkgCves detects OS pkg cves
// pass 2 configs
func DetectPkgCves(r *models.ScanResult, ovalCnf config.GovalDictConf, gostCnf config.GostConf, logOpts logging.LogOpts) error {
func DetectPkgCves(r *models.ScanResult, ovalCnf config.GovalDictConf, gostCnf config.GostConf, vuls2Cnf config.Vuls2DictConf, logOpts logging.LogOpts, noProgress bool) error {
// Pkg Scan
if isPkgCvesDetactable(r) {
// OVAL, gost(Debian Security Tracker) does not support Package for Raspbian, so skip it.
if r.Family == constant.Raspbian {
r = r.RemoveRaspbianPackFromResult()
}

// Vuls2
if err := vuls2.Detect(r, vuls2Cnf, noProgress); err != nil {
return xerrors.Errorf("Failed to detect CVE with Vuls2: %w", err)
}

// OVAL
if err := detectPkgsCvesWithOval(ovalCnf, r, logOpts); err != nil {
return xerrors.Errorf("Failed to detect CVE with OVAL: %w", err)
Expand Down Expand Up @@ -537,6 +543,9 @@
logging.Log.Infof("Skip OVAL and Scan with gost alone.")
logging.Log.Infof("%s: %d CVEs are detected with OVAL", r.FormatServerName(), 0)
return nil
case constant.RedHat, constant.CentOS, constant.Alma, constant.Rocky:
logging.Log.Debugf("Skip OVAL and Scan by Vuls2")
return nil
case constant.Windows, constant.MacOSX, constant.MacOSXServer, constant.MacOS, constant.MacOSServer, constant.FreeBSD, constant.ServerTypePseudo:
return nil
default:
Expand Down Expand Up @@ -657,7 +666,7 @@
return nil
}

func getMaxConfidence(detail cvemodels.CveDetail) (max models.Confidence) {

Check failure on line 669 in detector/detector.go

View workflow job for this annotation

GitHub Actions / Build

redefinition of the built-in function max https://revive.run/r#redefines-builtin-id
if detail.HasFortinet() {
for _, fortinet := range detail.Fortinets {
confidence := models.Confidence{}
Expand All @@ -670,7 +679,7 @@
confidence = models.FortinetVendorProductMatch
}
if max.Score < confidence.Score {
max = confidence

Check failure on line 682 in detector/detector.go

View workflow job for this annotation

GitHub Actions / Build

redefinition of the built-in function max https://revive.run/r#redefines-builtin-id
}
}
return max
Expand All @@ -688,7 +697,7 @@
confidence = models.NvdVendorProductMatch
}
if max.Score < confidence.Score {
max = confidence

Check failure on line 700 in detector/detector.go

View workflow job for this annotation

GitHub Actions / Build

redefinition of the built-in function max https://revive.run/r#redefines-builtin-id
}
}
return max
Expand Down
93 changes: 93 additions & 0 deletions detector/vuls2/db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package vuls2

Check failure on line 1 in detector/vuls2/db.go

View workflow job for this annotation

GitHub Actions / Analyze (go)

should have a package comment https://revive.run/r#package-comments

import (
"fmt"
"os"
"path/filepath"
"time"

"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/logging"
"github.com/pkg/errors"
"golang.org/x/xerrors"

db "github.com/MaineK00n/vuls2/pkg/db/common"
"github.com/MaineK00n/vuls2/pkg/db/fetch"
)

var (
// DefaultGHCRRepository is GitHub Container Registry for vuls2 db
DefaultGHCRRepository = fmt.Sprintf("%s:%d", "ghcr.io/vulsio/vuls-nightly-db", db.SchemaVersion)

// DefaultPath is the path for vuls2 db file
DefaultPath = func() string {
wd, _ := os.Getwd()
return filepath.Join(wd, "vuls.db")
}()
)

func newDBConnection(vuls2Cnf config.Vuls2DictConf, noProgress bool) (db.DB, error) {
willDownload, err := shouldDownload(vuls2Cnf, time.Now())
if err != nil {
return nil, xerrors.Errorf("Failed to check whether to download vuls2 db. err: %w", err)
}

if willDownload {
logging.Log.Infof("Downloading vuls2 db. repository: %s", vuls2Cnf.Repository)
if err := fetch.Fetch(fetch.WithRepository(vuls2Cnf.Repository), fetch.WithDBPath(vuls2Cnf.Path), fetch.WithNoProgress(noProgress)); err != nil {
return nil, xerrors.Errorf("Failed to fetch vuls2 db. err: %w", err)
}
}

dbc, err := (&db.Config{
Type: "boltdb",
Path: vuls2Cnf.Path,
}).New()
if err != nil {
return nil, xerrors.Errorf("Failed to new vuls2 db connection. err: %w", err)
}

return dbc, nil
}

func shouldDownload(vuls2Cnf config.Vuls2DictConf, now time.Time) (bool, error) {
if _, err := os.Stat(vuls2Cnf.Path); err != nil {
if errors.Is(err, os.ErrNotExist) {
if vuls2Cnf.SkipUpdate {
return false, xerrors.Errorf("%s not found, cannot skip update", vuls2Cnf.Path)
}
return true, nil
}
return false, xerrors.Errorf("Failed to stat vuls2 db file. err: %w", err)
}

if vuls2Cnf.SkipUpdate {
return false, nil
}

dbc, err := (&db.Config{
Type: "boltdb",
Path: vuls2Cnf.Path,
}).New()
if err != nil {
return false, xerrors.Errorf("Failed to new vuls2 db connection. path: %s, err: %w", vuls2Cnf.Path, err)
}

if err := dbc.Open(); err != nil {
return false, xerrors.Errorf("Failed to open vuls2 db. path: %s, err: %w", vuls2Cnf.Path, err)
}
defer dbc.Close()

metadata, err := dbc.GetMetadata()
if err != nil {
return false, xerrors.Errorf("Failed to get vuls2 db metadata. path: %s, err: %w", vuls2Cnf.Path, err)
}
if metadata == nil {
return false, xerrors.Errorf("Unexpected Vuls2 db metadata. metadata: nil,. path: %s", vuls2Cnf.Path)
}

if metadata.Downloaded != nil && now.Before((*metadata.Downloaded).Add(1*time.Hour)) {
return false, nil
}
return metadata.LastModified.Add(6 * time.Hour).Before(now), nil
}
149 changes: 149 additions & 0 deletions detector/vuls2/db_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package vuls2

import (
"path/filepath"
"reflect"
"testing"
"time"

"golang.org/x/xerrors"

"github.com/MaineK00n/vuls2/pkg/db/common"
"github.com/MaineK00n/vuls2/pkg/db/common/types"
"github.com/future-architect/vuls/config"
)

func Test_shouldDownload(t *testing.T) {
type args struct {
vuls2Cnf config.Vuls2DictConf
now time.Time
}
tests := []struct {
name string
args args
metadata *types.Metadata
want bool
wantErr bool
}{
{
name: "no db file",
args: args{
vuls2Cnf: config.Vuls2DictConf{},
now: *parse("2024-01-02T00:00:00Z"),
},
want: true,
},
{
name: "no db file, but skip update",
args: args{
vuls2Cnf: config.Vuls2DictConf{
SkipUpdate: true,
},
now: *parse("2024-01-02T00:00:00Z"),
},
wantErr: true,
},
{
name: "just created",
args: args{
vuls2Cnf: config.Vuls2DictConf{},
now: *parse("2024-01-02T00:00:00Z"),
},
metadata: &types.Metadata{
LastModified: *parse("2024-01-02T00:00:00Z"),
Downloaded: parse("2024-01-02T00:00:00Z"),
SchemaVersion: common.SchemaVersion,
},
want: false,
},
{
name: "8 hours old",
args: args{
vuls2Cnf: config.Vuls2DictConf{},
now: *parse("2024-01-02T08:00:00Z"),
},
metadata: &types.Metadata{
LastModified: *parse("2024-01-02T00:00:00Z"),
Downloaded: parse("2024-01-02T00:00:00Z"),
SchemaVersion: common.SchemaVersion,
},
want: true,
},
{
name: "8 hours old, but skip update",
args: args{
vuls2Cnf: config.Vuls2DictConf{
SkipUpdate: true,
},
now: *parse("2024-01-02T08:00:00Z"),
},
metadata: &types.Metadata{
LastModified: *parse("2024-01-02T00:00:00Z"),
Downloaded: parse("2024-01-02T00:00:00Z"),
SchemaVersion: common.SchemaVersion,
},
want: false,
},
{
name: "8 hours old, but download recently",
args: args{
vuls2Cnf: config.Vuls2DictConf{},
now: *parse("2024-01-02T08:00:00Z"),
},
metadata: &types.Metadata{
LastModified: *parse("2024-01-02T00:00:00Z"),
Downloaded: parse("2024-01-02T07:30:00Z"),
SchemaVersion: common.SchemaVersion,
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := t.TempDir()
tt.args.vuls2Cnf.Path = filepath.Join(d, "vuls.db")
if tt.metadata != nil {
if err := putMetadata(*tt.metadata, tt.args.vuls2Cnf.Path); err != nil {
t.Errorf("putMetadata err = %v", err)
return
}
}
got, err := shouldDownload(tt.args.vuls2Cnf, tt.args.now)
if (err != nil) != tt.wantErr {
t.Errorf("shouldDownload() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("shouldDownload() = %v, want %v", got, tt.want)
}
})
}

}

func putMetadata(metadata types.Metadata, path string) error {
c := common.Config{
Type: "boltdb",
Path: path,
}
dbc, err := c.New()
if err != nil {
return xerrors.Errorf("c.New(). err: %w", err)
}
if err := dbc.Open(); err != nil {
return xerrors.Errorf("dbc.Open(). err: %w", err)
}
defer dbc.Close()
if err := dbc.Initialize(); err != nil {
return xerrors.Errorf("dbc.Initialize(). err: %w", err)
}
if err := dbc.PutMetadata(metadata); err != nil {
return xerrors.Errorf("dbc.PutMetadata(). err: %w", err)
}
return nil
}

func parse(date string) *time.Time {
t, _ := time.Parse(time.RFC3339, date)
return &t
}
Loading
Loading