Add report subcommand, change scan options. Bump up ver #239
This commit is contained in:
249
models/models.go
249
models/models.go
@@ -23,15 +23,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/future-architect/vuls/cveapi"
|
||||
cve "github.com/kotakanbe/go-cve-dictionary/models"
|
||||
)
|
||||
|
||||
// ScanHistory is the history of Scanning.
|
||||
type ScanHistory struct {
|
||||
gorm.Model
|
||||
ScanResults ScanResults
|
||||
ScannedAt time.Time
|
||||
}
|
||||
|
||||
// ScanResults is slice of ScanResult.
|
||||
@@ -55,43 +53,111 @@ func (s ScanResults) Less(i, j int) bool {
|
||||
return s[i].ServerName < s[j].ServerName
|
||||
}
|
||||
|
||||
// FilterByCvssOver is filter function.
|
||||
func (s ScanResults) FilterByCvssOver() (filtered ScanResults) {
|
||||
for _, result := range s {
|
||||
cveInfos := []CveInfo{}
|
||||
for _, cveInfo := range result.KnownCves {
|
||||
if config.Conf.CvssScoreOver < cveInfo.CveDetail.CvssScore(config.Conf.Lang) {
|
||||
cveInfos = append(cveInfos, cveInfo)
|
||||
}
|
||||
}
|
||||
result.KnownCves = cveInfos
|
||||
filtered = append(filtered, result)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ScanResult has the result of scanned CVE information.
|
||||
type ScanResult struct {
|
||||
gorm.Model `json:"-" xml:"-"`
|
||||
ScanHistoryID uint `json:"-" xml:"-"`
|
||||
ScannedAt time.Time
|
||||
ScannedAt time.Time
|
||||
|
||||
Lang string
|
||||
ServerName string // TOML Section key
|
||||
// Hostname string
|
||||
Family string
|
||||
Release string
|
||||
Family string
|
||||
Release string
|
||||
Container Container
|
||||
Platform Platform
|
||||
|
||||
Container Container
|
||||
// Scanned Vulns via SSH + CPE Vulns
|
||||
ScannedCves []VulnInfo
|
||||
|
||||
Platform Platform
|
||||
|
||||
// Fqdn string
|
||||
// NWLinks []NWLink
|
||||
KnownCves []CveInfo
|
||||
UnknownCves []CveInfo
|
||||
IgnoredCves []CveInfo
|
||||
|
||||
Optional [][]interface{} `gorm:"-"`
|
||||
Packages PackageInfoList
|
||||
|
||||
Optional [][]interface{}
|
||||
}
|
||||
|
||||
// FillCveDetail fetches CVE detailed information from
|
||||
// CVE Database, and then set to fields.
|
||||
func (r ScanResult) FillCveDetail() (ScanResult, error) {
|
||||
set := map[string]VulnInfo{}
|
||||
var cveIDs []string
|
||||
for _, v := range r.ScannedCves {
|
||||
set[v.CveID] = v
|
||||
cveIDs = append(cveIDs, v.CveID)
|
||||
}
|
||||
|
||||
ds, err := cveapi.CveClient.FetchCveDetails(cveIDs)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
|
||||
icves := config.Conf.Servers[r.ServerName].IgnoreCves
|
||||
|
||||
var known, unknown, ignored CveInfos
|
||||
for _, d := range ds {
|
||||
cinfo := CveInfo{
|
||||
CveDetail: d,
|
||||
VulnInfo: set[d.CveID],
|
||||
}
|
||||
|
||||
// ignored
|
||||
found := false
|
||||
for _, icve := range icves {
|
||||
if icve == d.CveID {
|
||||
ignored = append(ignored, cinfo)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
continue
|
||||
}
|
||||
|
||||
// unknown
|
||||
if d.CvssScore(config.Conf.Lang) <= 0 {
|
||||
unknown = append(unknown, cinfo)
|
||||
continue
|
||||
}
|
||||
|
||||
// known
|
||||
known = append(known, cinfo)
|
||||
}
|
||||
sort.Sort(known)
|
||||
sort.Sort(unknown)
|
||||
sort.Sort(ignored)
|
||||
r.KnownCves = known
|
||||
r.UnknownCves = unknown
|
||||
r.IgnoredCves = ignored
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// FilterByCvssOver is filter function.
|
||||
func (r ScanResult) FilterByCvssOver() ScanResult {
|
||||
cveInfos := []CveInfo{}
|
||||
for _, cveInfo := range r.KnownCves {
|
||||
if config.Conf.CvssScoreOver < cveInfo.CveDetail.CvssScore(config.Conf.Lang) {
|
||||
cveInfos = append(cveInfos, cveInfo)
|
||||
}
|
||||
}
|
||||
r.KnownCves = cveInfos
|
||||
return r
|
||||
}
|
||||
|
||||
// ReportFileName returns the filename on localhost without extention
|
||||
func (r ScanResult) ReportFileName() (name string) {
|
||||
if len(r.Container.ContainerID) == 0 {
|
||||
return fmt.Sprintf("%s", r.ServerName)
|
||||
}
|
||||
return fmt.Sprintf("%s@%s", r.Container.Name, r.ServerName)
|
||||
}
|
||||
|
||||
// ReportKeyName returns the name of key on S3, Azure-Blob without extention
|
||||
func (r ScanResult) ReportKeyName() (name string) {
|
||||
timestr := r.ScannedAt.Format(time.RFC3339)
|
||||
if len(r.Container.ContainerID) == 0 {
|
||||
return fmt.Sprintf("%s/%s", timestr, r.ServerName)
|
||||
}
|
||||
return fmt.Sprintf("%s/%s@%s", timestr, r.Container.Name, r.ServerName)
|
||||
}
|
||||
|
||||
// ServerInfo returns server name one line
|
||||
@@ -141,15 +207,15 @@ func (r ScanResult) ServerInfoTui() string {
|
||||
|
||||
// CveSummary summarize the number of CVEs group by CVSSv2 Severity
|
||||
func (r ScanResult) CveSummary() string {
|
||||
var high, middle, low, unknown int
|
||||
var high, medium, low, unknown int
|
||||
cves := append(r.KnownCves, r.UnknownCves...)
|
||||
for _, cveInfo := range cves {
|
||||
score := cveInfo.CveDetail.CvssScore(config.Conf.Lang)
|
||||
switch {
|
||||
case 7.0 < score:
|
||||
case 7.0 <= score:
|
||||
high++
|
||||
case 4.0 < score:
|
||||
middle++
|
||||
case 4.0 <= score:
|
||||
medium++
|
||||
case 0 < score:
|
||||
low++
|
||||
default:
|
||||
@@ -158,11 +224,11 @@ func (r ScanResult) CveSummary() string {
|
||||
}
|
||||
|
||||
if config.Conf.IgnoreUnscoredCves {
|
||||
return fmt.Sprintf("Total: %d (High:%d Middle:%d Low:%d)",
|
||||
high+middle+low, high, middle, low)
|
||||
return fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d)",
|
||||
high+medium+low, high, medium, low)
|
||||
}
|
||||
return fmt.Sprintf("Total: %d (High:%d Middle:%d Low:%d ?:%d)",
|
||||
high+middle+low+unknown, high, middle, low, unknown)
|
||||
return fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d ?:%d)",
|
||||
high+medium+low+unknown, high, medium, low, unknown)
|
||||
}
|
||||
|
||||
// AllCves returns Known and Unknown CVEs
|
||||
@@ -172,15 +238,59 @@ func (r ScanResult) AllCves() []CveInfo {
|
||||
|
||||
// NWLink has network link information.
|
||||
type NWLink struct {
|
||||
gorm.Model `json:"-" xml:"-"`
|
||||
ScanResultID uint `json:"-" xml:"-"`
|
||||
|
||||
IPAddress string
|
||||
Netmask string
|
||||
DevName string
|
||||
LinkState string
|
||||
}
|
||||
|
||||
// VulnInfos is VulnInfo list, getter/setter, sortable methods.
|
||||
type VulnInfos []VulnInfo
|
||||
|
||||
// VulnInfo holds a vulnerability information and unsecure packages
|
||||
type VulnInfo struct {
|
||||
CveID string
|
||||
Packages PackageInfoList
|
||||
DistroAdvisories []DistroAdvisory // for Aamazon, RHEL, FreeBSD
|
||||
CpeNames []string
|
||||
}
|
||||
|
||||
// FindByCveID find by CVEID
|
||||
func (s VulnInfos) FindByCveID(cveID string) (VulnInfo, bool) {
|
||||
for _, p := range s {
|
||||
if cveID == p.CveID {
|
||||
return p, true
|
||||
}
|
||||
}
|
||||
return VulnInfo{CveID: cveID}, false
|
||||
}
|
||||
|
||||
// immutable
|
||||
func (s VulnInfos) set(cveID string, v VulnInfo) VulnInfos {
|
||||
for i, p := range s {
|
||||
if cveID == p.CveID {
|
||||
s[i] = v
|
||||
return s
|
||||
}
|
||||
}
|
||||
return append(s, v)
|
||||
}
|
||||
|
||||
// Len implement Sort Interface
|
||||
func (s VulnInfos) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
// Swap implement Sort Interface
|
||||
func (s VulnInfos) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
// Less implement Sort Interface
|
||||
func (s VulnInfos) Less(i, j int) bool {
|
||||
return s[i].CveID < s[j].CveID
|
||||
}
|
||||
|
||||
// CveInfos is for sorting
|
||||
type CveInfos []CveInfo
|
||||
|
||||
@@ -202,21 +312,8 @@ func (c CveInfos) Less(i, j int) bool {
|
||||
|
||||
// CveInfo has Cve Information.
|
||||
type CveInfo struct {
|
||||
gorm.Model `json:"-" xml:"-"`
|
||||
ScanResultID uint `json:"-" xml:"-"`
|
||||
|
||||
CveDetail cve.CveDetail
|
||||
Packages []PackageInfo
|
||||
DistroAdvisories []DistroAdvisory
|
||||
CpeNames []CpeName
|
||||
}
|
||||
|
||||
// CpeName has CPE name
|
||||
type CpeName struct {
|
||||
gorm.Model `json:"-" xml:"-"`
|
||||
CveInfoID uint `json:"-" xml:"-"`
|
||||
|
||||
Name string
|
||||
CveDetail cve.CveDetail
|
||||
VulnInfo
|
||||
}
|
||||
|
||||
// PackageInfoList is slice of PackageInfo
|
||||
@@ -260,6 +357,34 @@ func (ps PackageInfoList) FindByName(name string) (result PackageInfo, found boo
|
||||
return PackageInfo{}, false
|
||||
}
|
||||
|
||||
// MergeNewVersion merges candidate version information to the receiver struct
|
||||
func (ps PackageInfoList) MergeNewVersion(as PackageInfoList) {
|
||||
for _, a := range as {
|
||||
for i, p := range ps {
|
||||
if p.Name == a.Name {
|
||||
ps[i].NewVersion = a.NewVersion
|
||||
ps[i].NewRelease = a.NewRelease
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ps PackageInfoList) countUpdatablePacks() int {
|
||||
count := 0
|
||||
for _, p := range ps {
|
||||
if len(p.NewVersion) != 0 {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// ToUpdatablePacksSummary returns a summary of updatable packages
|
||||
func (ps PackageInfoList) ToUpdatablePacksSummary() string {
|
||||
return fmt.Sprintf("%d updatable packages",
|
||||
ps.countUpdatablePacks())
|
||||
}
|
||||
|
||||
// Find search PackageInfo by name-version-release
|
||||
// func (ps PackageInfoList) find(nameVersionRelease string) (PackageInfo, bool) {
|
||||
// for _, p := range ps {
|
||||
@@ -287,9 +412,6 @@ func (a PackageInfosByName) Less(i, j int) bool { return a[i].Name < a[j].Name }
|
||||
|
||||
// PackageInfo has installed packages.
|
||||
type PackageInfo struct {
|
||||
gorm.Model `json:"-" xml:"-"`
|
||||
CveInfoID uint `json:"-" xml:"-"`
|
||||
|
||||
Name string
|
||||
Version string
|
||||
Release string
|
||||
@@ -324,9 +446,6 @@ func (p PackageInfo) ToStringNewVersion() string {
|
||||
|
||||
// DistroAdvisory has Amazon Linux, RHEL, FreeBSD Security Advisory information.
|
||||
type DistroAdvisory struct {
|
||||
gorm.Model `json:"-" xml:"-"`
|
||||
CveInfoID uint `json:"-" xml:"-"`
|
||||
|
||||
AdvisoryID string
|
||||
Severity string
|
||||
Issued time.Time
|
||||
@@ -335,18 +454,12 @@ type DistroAdvisory struct {
|
||||
|
||||
// Container has Container information
|
||||
type Container struct {
|
||||
gorm.Model `json:"-" xml:"-"`
|
||||
ScanResultID uint `json:"-" xml:"-"`
|
||||
|
||||
ContainerID string
|
||||
Name string
|
||||
}
|
||||
|
||||
// Platform has platform information
|
||||
type Platform struct {
|
||||
gorm.Model `json:"-" xml:"-"`
|
||||
ScanResultID uint `json:"-" xml:"-"`
|
||||
|
||||
Name string // aws or azure or gcp or other...
|
||||
InstanceID string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user