Add report subcommand, change scan options. Bump up ver #239

This commit is contained in:
Kota Kanbe
2016-11-17 14:24:31 +09:00
parent cb29289167
commit 155cadf901
43 changed files with 2761 additions and 1979 deletions

View File

@@ -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
}

View File

@@ -17,9 +17,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package models
import "testing"
import (
"reflect"
"testing"
func TestPackageInfosUniqByName(t *testing.T) {
"github.com/k0kubun/pp"
)
func TestPackageInfoListUniqByName(t *testing.T) {
var test = struct {
in PackageInfoList
out PackageInfoList
@@ -52,3 +57,81 @@ func TestPackageInfosUniqByName(t *testing.T) {
}
}
}
func TestMergeNewVersion(t *testing.T) {
var test = struct {
a PackageInfoList
b PackageInfoList
expected PackageInfoList
}{
PackageInfoList{
{
Name: "hoge",
},
},
PackageInfoList{
{
Name: "hoge",
NewVersion: "1.0.0",
NewRelease: "release1",
},
},
PackageInfoList{
{
Name: "hoge",
NewVersion: "1.0.0",
NewRelease: "release1",
},
},
}
test.a.MergeNewVersion(test.b)
if !reflect.DeepEqual(test.a, test.expected) {
e := pp.Sprintf("%v", test.a)
a := pp.Sprintf("%v", test.expected)
t.Errorf("expected %s, actual %s", e, a)
}
}
func TestVulnInfosSetGet(t *testing.T) {
var test = struct {
in []string
out []string
}{
[]string{
"CVE1",
"CVE2",
"CVE3",
"CVE1",
"CVE1",
"CVE2",
"CVE3",
},
[]string{
"CVE1",
"CVE2",
"CVE3",
},
}
// var ps packageCveInfos
var ps VulnInfos
for _, cid := range test.in {
ps = ps.set(cid, VulnInfo{CveID: cid})
}
if len(test.out) != len(ps) {
t.Errorf("length: expected %d, actual %d", len(test.out), len(ps))
}
for i, expectedCid := range test.out {
if expectedCid != ps[i].CveID {
t.Errorf("expected %s, actual %s", expectedCid, ps[i].CveID)
}
}
for _, cid := range test.in {
p, _ := ps.FindByCveID(cid)
if p.CveID != cid {
t.Errorf("expected %s, actual %s", cid, p.CveID)
}
}
}