Support Debian
This commit is contained in:
@@ -50,6 +50,9 @@ type ReportCmd struct {
|
||||
cvedbpath string
|
||||
cvedbURL string
|
||||
|
||||
ovaldbtype string
|
||||
ovaldbpath string
|
||||
|
||||
toSlack bool
|
||||
toEMail bool
|
||||
toLocalFile bool
|
||||
@@ -162,6 +165,19 @@ func (p *ReportCmd) SetFlags(f *flag.FlagSet) {
|
||||
defaultCveDBPath,
|
||||
"/path/to/sqlite3 (For get cve detail from cve.sqlite3)")
|
||||
|
||||
f.StringVar(
|
||||
&p.ovaldbtype,
|
||||
"ovaldb-type",
|
||||
"sqlite3",
|
||||
"DB type for fetching OVAL dictionary (sqlite3 or mysql)")
|
||||
|
||||
defaultOvalDBPath := filepath.Join(wd, "oval.sqlite3")
|
||||
f.StringVar(
|
||||
&p.ovaldbpath,
|
||||
"ovaldb-path",
|
||||
defaultOvalDBPath,
|
||||
"/path/to/sqlite3 (For get oval detail from oval.sqlite3)")
|
||||
|
||||
f.StringVar(
|
||||
&p.cvedbURL,
|
||||
"cvedb-url",
|
||||
@@ -276,6 +292,8 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}
|
||||
c.Conf.CveDBType = p.cvedbtype
|
||||
c.Conf.CveDBPath = p.cvedbpath
|
||||
c.Conf.CveDBURL = p.cvedbURL
|
||||
c.Conf.OvalDBType = p.ovaldbtype
|
||||
c.Conf.OvalDBPath = p.ovaldbpath
|
||||
c.Conf.CvssScoreOver = p.cvssScoreOver
|
||||
c.Conf.IgnoreUnscoredCves = p.ignoreUnscoredCves
|
||||
c.Conf.HTTPProxy = p.httpProxy
|
||||
@@ -399,11 +417,18 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}
|
||||
}
|
||||
}
|
||||
|
||||
filled, err := fillCveInfoFromCveDB(r)
|
||||
filled, err := fillCveInfoFromOvalDB(r)
|
||||
if err != nil {
|
||||
util.Log.Errorf("Failed to fill OVAL information: %s", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
filled, err = fillCveInfoFromCveDB(*filled)
|
||||
if err != nil {
|
||||
util.Log.Errorf("Failed to fill CVE information: %s", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
filled.Lang = c.Conf.Lang
|
||||
if err := overwriteJSONFile(dir, *filled); err != nil {
|
||||
util.Log.Errorf("Failed to write JSON: %s", err)
|
||||
|
||||
@@ -35,19 +35,20 @@ import (
|
||||
|
||||
// ScanCmd is Subcommand of host discovery mode
|
||||
type ScanCmd struct {
|
||||
debug bool
|
||||
configPath string
|
||||
resultsDir string
|
||||
logDir string
|
||||
cacheDBPath string
|
||||
httpProxy string
|
||||
askKeyPassword bool
|
||||
containersOnly bool
|
||||
skipBroken bool
|
||||
sshNative bool
|
||||
pipe bool
|
||||
timeoutSec int
|
||||
scanTimeoutSec int
|
||||
debug bool
|
||||
configPath string
|
||||
resultsDir string
|
||||
logDir string
|
||||
cacheDBPath string
|
||||
httpProxy string
|
||||
askKeyPassword bool
|
||||
containersOnly bool
|
||||
packageListOnly bool
|
||||
skipBroken bool
|
||||
sshNative bool
|
||||
pipe bool
|
||||
timeoutSec int
|
||||
scanTimeoutSec int
|
||||
}
|
||||
|
||||
// Name return subcommand name
|
||||
@@ -132,6 +133,12 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
|
||||
"Ask ssh privatekey password before scanning",
|
||||
)
|
||||
|
||||
f.BoolVar(
|
||||
&p.packageListOnly,
|
||||
"package-list-only",
|
||||
false,
|
||||
"List all packages without scan")
|
||||
|
||||
f.BoolVar(
|
||||
&p.pipe,
|
||||
"pipe",
|
||||
@@ -223,6 +230,7 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
|
||||
c.Conf.SSHNative = p.sshNative
|
||||
c.Conf.HTTPProxy = p.httpProxy
|
||||
c.Conf.ContainersOnly = p.containersOnly
|
||||
c.Conf.PackageListOnly = p.packageListOnly
|
||||
c.Conf.SkipBroken = p.skipBroken
|
||||
|
||||
util.Log.Info("Validating config...")
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
c "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/cveapi"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/oval"
|
||||
"github.com/future-architect/vuls/report"
|
||||
"github.com/future-architect/vuls/util"
|
||||
)
|
||||
@@ -180,6 +181,23 @@ func fillCveInfoFromCveDB(r models.ScanResult) (*models.ScanResult, error) {
|
||||
return r.FillCveDetail()
|
||||
}
|
||||
|
||||
func fillCveInfoFromOvalDB(r models.ScanResult) (*models.ScanResult, error) {
|
||||
var ovalClient oval.OvalClient
|
||||
switch r.Family {
|
||||
case "ubuntu", "debian":
|
||||
ovalClient = oval.NewDebian()
|
||||
fmt.Println("hello")
|
||||
case "redhat":
|
||||
// TODO: RedHat
|
||||
// ovalClient = oval.NewRedhat()
|
||||
}
|
||||
result, err := ovalClient.FillCveInfoFromOvalDB(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func loadPreviousScanHistory(current models.ScanHistory) (previous models.ScanHistory, err error) {
|
||||
var dirs jsonDirs
|
||||
if dirs, err = lsValidJSONDirs(); err != nil {
|
||||
|
||||
@@ -44,9 +44,10 @@ type Config struct {
|
||||
CvssScoreOver float64
|
||||
IgnoreUnscoredCves bool
|
||||
|
||||
SSHNative bool
|
||||
ContainersOnly bool
|
||||
SkipBroken bool
|
||||
SSHNative bool
|
||||
ContainersOnly bool
|
||||
PackageListOnly bool
|
||||
SkipBroken bool
|
||||
|
||||
HTTPProxy string `valid:"url"`
|
||||
LogDir string
|
||||
@@ -57,6 +58,9 @@ type Config struct {
|
||||
CveDBURL string
|
||||
CacheDBPath string
|
||||
|
||||
OvalDBType string
|
||||
OvalDBPath string
|
||||
|
||||
FormatXML bool
|
||||
FormatJSON bool
|
||||
FormatOneEMail bool
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/cveapi"
|
||||
cve "github.com/kotakanbe/go-cve-dictionary/models"
|
||||
goval "github.com/kotakanbe/goval-dictionary/models"
|
||||
)
|
||||
|
||||
// ScanHistory is the history of Scanning.
|
||||
@@ -67,9 +68,9 @@ type ScanResult struct {
|
||||
// Scanned Vulns via SSH + CPE Vulns
|
||||
ScannedCves []VulnInfo
|
||||
|
||||
KnownCves []CveInfo
|
||||
UnknownCves []CveInfo
|
||||
IgnoredCves []CveInfo
|
||||
KnownCves CveInfos
|
||||
UnknownCves CveInfos
|
||||
IgnoredCves CveInfos
|
||||
|
||||
Packages PackageInfoList
|
||||
|
||||
@@ -92,7 +93,7 @@ func (r ScanResult) FillCveDetail() (*ScanResult, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
known, unknown, ignored := CveInfos{}, CveInfos{}, CveInfos{}
|
||||
r.IgnoredCves = CveInfos{}
|
||||
for _, d := range ds {
|
||||
cinfo := CveInfo{
|
||||
CveDetail: d,
|
||||
@@ -104,7 +105,7 @@ func (r ScanResult) FillCveDetail() (*ScanResult, error) {
|
||||
found := false
|
||||
for _, icve := range config.Conf.Servers[r.ServerName].IgnoreCves {
|
||||
if icve == d.CveID {
|
||||
ignored = append(ignored, cinfo)
|
||||
r.IgnoredCves.Insert(cinfo)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
@@ -113,29 +114,45 @@ func (r ScanResult) FillCveDetail() (*ScanResult, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Update known if KnownCves already have cinfo
|
||||
if c, ok := r.KnownCves.Get(cinfo.CveID); ok {
|
||||
c.CveDetail = d
|
||||
r.KnownCves.Update(c)
|
||||
continue
|
||||
}
|
||||
|
||||
// Update unknown if UnknownCves already have cinfo
|
||||
if c, ok := r.UnknownCves.Get(cinfo.CveID); ok {
|
||||
c.CveDetail = d
|
||||
r.UnknownCves.Update(c)
|
||||
continue
|
||||
}
|
||||
|
||||
// unknown
|
||||
if d.CvssScore(config.Conf.Lang) <= 0 {
|
||||
unknown = append(unknown, cinfo)
|
||||
r.UnknownCves.Insert(cinfo)
|
||||
continue
|
||||
}
|
||||
|
||||
// known
|
||||
known = append(known, cinfo)
|
||||
r.KnownCves.Insert(cinfo)
|
||||
}
|
||||
sort.Sort(known)
|
||||
sort.Sort(unknown)
|
||||
sort.Sort(ignored)
|
||||
r.KnownCves = known
|
||||
r.UnknownCves = unknown
|
||||
r.IgnoredCves = ignored
|
||||
sort.Sort(r.KnownCves)
|
||||
sort.Sort(r.UnknownCves)
|
||||
sort.Sort(r.IgnoredCves)
|
||||
return &r, nil
|
||||
}
|
||||
|
||||
// FilterByCvssOver is filter function.
|
||||
func (r ScanResult) FilterByCvssOver() ScanResult {
|
||||
cveInfos := []CveInfo{}
|
||||
// TODO: Set correct default value
|
||||
if config.Conf.CvssScoreOver == 0 {
|
||||
config.Conf.CvssScoreOver = -1.1
|
||||
}
|
||||
|
||||
for _, cveInfo := range r.KnownCves {
|
||||
if config.Conf.CvssScoreOver < cveInfo.CveDetail.CvssScore(config.Conf.Lang) {
|
||||
if config.Conf.CvssScoreOver <= cveInfo.CveDetail.CvssScore(config.Conf.Lang) {
|
||||
cveInfos = append(cveInfos, cveInfo)
|
||||
}
|
||||
}
|
||||
@@ -260,6 +277,9 @@ const (
|
||||
// PkgAuditMatchStr is a String representation of PkgAuditMatch
|
||||
PkgAuditMatchStr = "PkgAuditMatch"
|
||||
|
||||
// OvalMatchStr is a String representation of OvalMatch
|
||||
OvalMatchStr = "OvalMatch"
|
||||
|
||||
// ChangelogExactMatchStr is a String representation of ChangelogExactMatch
|
||||
ChangelogExactMatchStr = "ChangelogExactMatch"
|
||||
|
||||
@@ -282,6 +302,9 @@ var YumUpdateSecurityMatch = Confidence{100, YumUpdateSecurityMatchStr}
|
||||
// PkgAuditMatch is a ranking how confident the CVE-ID was deteted correctly
|
||||
var PkgAuditMatch = Confidence{100, PkgAuditMatchStr}
|
||||
|
||||
// OvalMatch is a ranking how confident the CVE-ID was deteted correctly
|
||||
var OvalMatch = Confidence{100, OvalMatchStr}
|
||||
|
||||
// ChangelogExactMatch is a ranking how confident the CVE-ID was deteted correctly
|
||||
var ChangelogExactMatch = Confidence{95, ChangelogExactMatchStr}
|
||||
|
||||
@@ -368,9 +391,50 @@ func (c CveInfos) Less(i, j int) bool {
|
||||
return c[j].CveDetail.CvssScore(lang) < c[i].CveDetail.CvssScore(lang)
|
||||
}
|
||||
|
||||
func (c CveInfos) Get(cveID string) (CveInfo, bool) {
|
||||
for _, cve := range c {
|
||||
if cve.VulnInfo.CveID == cveID {
|
||||
return cve, true
|
||||
}
|
||||
}
|
||||
return CveInfo{}, false
|
||||
}
|
||||
|
||||
func (c *CveInfos) Delete(cveID string) {
|
||||
cveInfos := *c
|
||||
for i, cve := range cveInfos {
|
||||
if cve.VulnInfo.CveID == cveID {
|
||||
*c = append(cveInfos[:i], cveInfos[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CveInfos) Insert(cveInfo CveInfo) {
|
||||
*c = append(*c, cveInfo)
|
||||
}
|
||||
|
||||
func (c CveInfos) Update(cveInfo CveInfo) (ok bool) {
|
||||
for i, cve := range c {
|
||||
if cve.VulnInfo.CveID == cveInfo.VulnInfo.CveID {
|
||||
c[i] = cveInfo
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *CveInfos) Upsert(cveInfo CveInfo) {
|
||||
ok := c.Update(cveInfo)
|
||||
if !ok {
|
||||
c.Insert(cveInfo)
|
||||
}
|
||||
}
|
||||
|
||||
// CveInfo has Cve Information.
|
||||
type CveInfo struct {
|
||||
CveDetail cve.CveDetail
|
||||
CveDetail cve.CveDetail
|
||||
OvalDetail goval.Definition
|
||||
VulnInfo
|
||||
}
|
||||
|
||||
|
||||
109
oval/debian.go
Normal file
109
oval/debian.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package oval
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
ver "github.com/knqyf263/go-deb-version"
|
||||
cve "github.com/kotakanbe/go-cve-dictionary/models"
|
||||
ovalconf "github.com/kotakanbe/goval-dictionary/config"
|
||||
db "github.com/kotakanbe/goval-dictionary/db"
|
||||
ovalmodels "github.com/kotakanbe/goval-dictionary/models"
|
||||
)
|
||||
|
||||
// Debian is the interface for Debian OVAL
|
||||
type Debian struct{}
|
||||
|
||||
// NewDebian creates OVAL client for Debian
|
||||
func NewDebian() Debian {
|
||||
return Debian{}
|
||||
}
|
||||
|
||||
// FillCveInfoFromOvalDB returns scan result after updating CVE info by OVAL
|
||||
func (o Debian) FillCveInfoFromOvalDB(r models.ScanResult) (*models.ScanResult, error) {
|
||||
util.Log.Debugf("open oval-dictionary db (%s)", config.Conf.OvalDBType)
|
||||
ovalconf.Conf.DBType = config.Conf.OvalDBType
|
||||
ovalconf.Conf.DBPath = config.Conf.OvalDBPath
|
||||
|
||||
if err := db.OpenDB(); err != nil {
|
||||
return nil, fmt.Errorf("Failed to open OVAL DB. err: %s", err)
|
||||
}
|
||||
|
||||
d := db.NewDebian()
|
||||
for _, pack := range r.Packages {
|
||||
// TODO: Set the correct release after implementing LIKE in goval-dictionary
|
||||
definitions, err := d.GetByPackName("8.2", pack.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to get OVAL info by package name: %v", err)
|
||||
}
|
||||
for _, definition := range definitions {
|
||||
current, _ := ver.NewVersion(pack.Version)
|
||||
for _, p := range definition.AffectedPacks {
|
||||
if pack.Name != p.Name {
|
||||
continue
|
||||
}
|
||||
affected, _ := ver.NewVersion(p.Version)
|
||||
if current.LessThan(affected) {
|
||||
r = o.fillOvalInfo(r, definition)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return &r, nil
|
||||
}
|
||||
|
||||
func (o Debian) fillOvalInfo(r models.ScanResult, definition ovalmodels.Definition) models.ScanResult {
|
||||
// Update ScannedCves by OVAL info
|
||||
found := false
|
||||
cves := []models.VulnInfo{}
|
||||
for _, cve := range r.ScannedCves {
|
||||
if cve.CveID == definition.Debian.CveID {
|
||||
found = true
|
||||
if cve.Confidence.Score < models.OvalMatch.Score {
|
||||
cve.Confidence = models.OvalMatch
|
||||
}
|
||||
}
|
||||
cves = append(cves, cve)
|
||||
}
|
||||
|
||||
packageInfoList := getPackageInfoList(r, definition)
|
||||
vuln := models.VulnInfo{
|
||||
CveID: definition.Debian.CveID,
|
||||
Confidence: models.OvalMatch,
|
||||
Packages: packageInfoList,
|
||||
}
|
||||
|
||||
if !found {
|
||||
cves = append(cves, vuln)
|
||||
}
|
||||
r.ScannedCves = cves
|
||||
|
||||
// Update KnownCves by OVAL info
|
||||
cveInfo, ok := r.KnownCves.Get(definition.Debian.CveID)
|
||||
if !ok {
|
||||
cveInfo.CveDetail = cve.CveDetail{
|
||||
CveID: definition.Debian.CveID,
|
||||
}
|
||||
cveInfo.VulnInfo = vuln
|
||||
}
|
||||
cveInfo.OvalDetail = definition
|
||||
if cveInfo.VulnInfo.Confidence.Score < models.OvalMatch.Score {
|
||||
cveInfo.Confidence = models.OvalMatch
|
||||
}
|
||||
r.KnownCves.Upsert(cveInfo)
|
||||
|
||||
// Update UnknownCves by OVAL info
|
||||
cveInfo, ok = r.UnknownCves.Get(definition.Debian.CveID)
|
||||
if ok {
|
||||
cveInfo.OvalDetail = definition
|
||||
if cveInfo.VulnInfo.Confidence.Score < models.OvalMatch.Score {
|
||||
cveInfo.Confidence = models.OvalMatch
|
||||
}
|
||||
r.UnknownCves.Delete(definition.Debian.CveID)
|
||||
r.KnownCves.Upsert(cveInfo)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
24
oval/oval.go
Normal file
24
oval/oval.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package oval
|
||||
|
||||
import (
|
||||
"github.com/future-architect/vuls/models"
|
||||
ovalmodels "github.com/kotakanbe/goval-dictionary/models"
|
||||
)
|
||||
|
||||
// OvalClient is the interface of OVAL client.
|
||||
type OvalClient interface {
|
||||
FillCveInfoFromOvalDB(r models.ScanResult) (*models.ScanResult, error)
|
||||
}
|
||||
|
||||
func getPackageInfoList(r models.ScanResult, d ovalmodels.Definition) models.PackageInfoList {
|
||||
var packageInfoList models.PackageInfoList
|
||||
for _, pack := range d.AffectedPacks {
|
||||
for _, p := range r.Packages {
|
||||
if pack.Name == p.Name {
|
||||
packageInfoList = append(packageInfoList, p)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return packageInfoList
|
||||
}
|
||||
11
oval/redhat.go
Normal file
11
oval/redhat.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package oval
|
||||
|
||||
type redhat struct{}
|
||||
|
||||
func NewRedhat() redhat {
|
||||
return redhat{}
|
||||
}
|
||||
|
||||
func (o redhat) FillCveInfoFromOvalDB() {
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user