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 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
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 Vuls2Conf `json:"vuls2,omitempty"`

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

// Vuls2Conf is configuration items for vuls2
type Vuls2Conf 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 @@ import (
"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 @@ func Detect(rs []models.ScanResult, dir string) ([]models.ScanResult, error) {
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 @@ func Detect(rs []models.ScanResult, dir string) ([]models.ScanResult, error) {

// 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, vuls2Conf config.Vuls2Conf, 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, vuls2Conf, 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 @@ func detectPkgsCvesWithOval(cnf config.GovalDictConf, r *models.ScanResult, logO
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
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

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(vuls2Conf config.Vuls2Conf, noProgress bool) (db.DB, error) {
willDownload, err := shouldDownload(vuls2Conf, 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("Fetching vuls2 db. repository: %s", vuls2Conf.Repository)
if err := fetch.Fetch(fetch.WithRepository(vuls2Conf.Repository), fetch.WithDBPath(vuls2Conf.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: vuls2Conf.Path,
}).New()
if err != nil {
return nil, xerrors.Errorf("Failed to new vuls2 db connection. err: %w", err)
}

return dbc, nil
}

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

if vuls2Conf.SkipUpdate {
return false, nil
}

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

if err := dbc.Open(); err != nil {
return false, xerrors.Errorf("Failed to open vuls2 db. path: %s, err: %w", vuls2Conf.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", vuls2Conf.Path, err)
}
if metadata == nil {
return false, xerrors.Errorf("Unexpected Vuls2 db metadata. metadata: nil,. path: %s", vuls2Conf.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 {
vuls2Conf config.Vuls2Conf
now time.Time
}
tests := []struct {
name string
args args
metadata *types.Metadata
want bool
wantErr bool
}{
{
name: "no db file",
args: args{
vuls2Conf: config.Vuls2Conf{},
now: *parse("2024-01-02T00:00:00Z"),
},
want: true,
},
{
name: "no db file, but skip update",
args: args{
vuls2Conf: config.Vuls2Conf{
SkipUpdate: true,
},
now: *parse("2024-01-02T00:00:00Z"),
},
wantErr: true,
},
{
name: "just created",
args: args{
vuls2Conf: config.Vuls2Conf{},
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{
vuls2Conf: config.Vuls2Conf{},
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{
vuls2Conf: config.Vuls2Conf{
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{
vuls2Conf: config.Vuls2Conf{},
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.vuls2Conf.Path = filepath.Join(d, "vuls.db")
if tt.metadata != nil {
if err := putMetadata(*tt.metadata, tt.args.vuls2Conf.Path); err != nil {
t.Errorf("putMetadata err = %v", err)
return
}
}
got, err := shouldDownload(tt.args.vuls2Conf, 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