Files
vuls/models/models.go
2017-02-09 01:05:12 -08:00

463 lines
11 KiB
Go

/* Vuls - Vulnerability Scanner
Copyright (C) 2016 Future Architect, Inc. Japan.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package models
import (
"fmt"
"sort"
"time"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/cveapi"
cve "github.com/kotakanbe/go-cve-dictionary/models"
)
// ScanHistory is the history of Scanning.
type ScanHistory struct {
ScanResults ScanResults
}
// ScanResults is slice of ScanResult.
type ScanResults []ScanResult
// Len implement Sort Interface
func (s ScanResults) Len() int {
return len(s)
}
// Swap implement Sort Interface
func (s ScanResults) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// Less implement Sort Interface
func (s ScanResults) Less(i, j int) bool {
if s[i].ServerName == s[j].ServerName {
return s[i].Container.ContainerID < s[i].Container.ContainerID
}
return s[i].ServerName < s[j].ServerName
}
// ScanResult has the result of scanned CVE information.
type ScanResult struct {
ScannedAt time.Time
Lang string
ServerName string // TOML Section key
Family string
Release string
Container Container
Platform Platform
// Scanned Vulns via SSH + CPE Vulns
ScannedCves []VulnInfo
KnownCves []CveInfo
UnknownCves []CveInfo
IgnoredCves []CveInfo
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
func (r ScanResult) ServerInfo() string {
if len(r.Container.ContainerID) == 0 {
return fmt.Sprintf("%s (%s%s)",
r.ServerName, r.Family, r.Release,
)
}
return fmt.Sprintf(
"%s / %s (%s%s) on %s",
r.Container.Name,
r.Container.ContainerID,
r.Family,
r.Release,
r.ServerName,
)
}
// ServerInfoTui returns server infromation for TUI sidebar
func (r ScanResult) ServerInfoTui() string {
if len(r.Container.ContainerID) == 0 {
return fmt.Sprintf("%s (%s%s)",
r.ServerName, r.Family, r.Release)
}
return fmt.Sprintf(
"|-- %s (%s%s)",
r.Container.Name,
r.Family,
r.Release,
// r.Container.ContainerID,
)
}
// FormatServerName returns server and contianer name
func (r ScanResult) FormatServerName() string {
if len(r.Container.ContainerID) == 0 {
return r.ServerName
}
return fmt.Sprintf("%s@%s",
r.Container.Name, r.ServerName)
}
// CveSummary summarize the number of CVEs group by CVSSv2 Severity
func (r ScanResult) CveSummary() string {
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:
high++
case 4.0 <= score:
medium++
case 0 < score:
low++
default:
unknown++
}
}
if config.Conf.IgnoreUnscoredCves {
return fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d)",
high+medium+low, high, medium, low)
}
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
func (r ScanResult) AllCves() []CveInfo {
return append(r.KnownCves, r.UnknownCves...)
}
// NWLink has network link information.
type NWLink struct {
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
func (c CveInfos) Len() int {
return len(c)
}
func (c CveInfos) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
}
func (c CveInfos) Less(i, j int) bool {
lang := config.Conf.Lang
if c[i].CveDetail.CvssScore(lang) == c[j].CveDetail.CvssScore(lang) {
return c[i].CveDetail.CveID < c[j].CveDetail.CveID
}
return c[j].CveDetail.CvssScore(lang) < c[i].CveDetail.CvssScore(lang)
}
// CveInfo has Cve Information.
type CveInfo struct {
CveDetail cve.CveDetail
VulnInfo
}
// PackageInfoList is slice of PackageInfo
type PackageInfoList []PackageInfo
// Exists returns true if exists the name
func (ps PackageInfoList) Exists(name string) bool {
for _, p := range ps {
if p.Name == name {
return true
}
}
return false
}
// UniqByName be uniq by name.
func (ps PackageInfoList) UniqByName() (distincted PackageInfoList) {
set := make(map[string]PackageInfo)
for _, p := range ps {
set[p.Name] = p
}
//sort by key
keys := []string{}
for key := range set {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
distincted = append(distincted, set[key])
}
return
}
// FindByName search PackageInfo by name
func (ps PackageInfoList) FindByName(name string) (result PackageInfo, found bool) {
for _, p := range ps {
if p.Name == name {
return p, true
}
}
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 {
// joined := p.Name
// if 0 < len(p.Version) {
// joined = fmt.Sprintf("%s-%s", joined, p.Version)
// }
// if 0 < len(p.Release) {
// joined = fmt.Sprintf("%s-%s", joined, p.Release)
// }
// if joined == nameVersionRelease {
// return p, true
// }
// }
// return PackageInfo{}, false
// }
// PackageInfosByName implements sort.Interface for []PackageInfo based on
// the Name field.
type PackageInfosByName []PackageInfo
func (a PackageInfosByName) Len() int { return len(a) }
func (a PackageInfosByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a PackageInfosByName) Less(i, j int) bool { return a[i].Name < a[j].Name }
// PackageInfo has installed packages.
type PackageInfo struct {
Name string
Version string
Release string
NewVersion string
NewRelease string
Repository string
}
// ToStringCurrentVersion returns package name-version-release
func (p PackageInfo) ToStringCurrentVersion() string {
str := p.Name
if 0 < len(p.Version) {
str = fmt.Sprintf("%s-%s", str, p.Version)
}
if 0 < len(p.Release) {
str = fmt.Sprintf("%s-%s", str, p.Release)
}
return str
}
// ToStringNewVersion returns package name-version-release
func (p PackageInfo) ToStringNewVersion() string {
str := p.Name
if 0 < len(p.NewVersion) {
str = fmt.Sprintf("%s-%s", str, p.NewVersion)
}
if 0 < len(p.NewRelease) {
str = fmt.Sprintf("%s-%s", str, p.NewRelease)
}
return str
}
// DistroAdvisory has Amazon Linux, RHEL, FreeBSD Security Advisory information.
type DistroAdvisory struct {
AdvisoryID string
Severity string
Issued time.Time
Updated time.Time
}
// Container has Container information
type Container struct {
ContainerID string
Name string
Image string
}
// Platform has platform information
type Platform struct {
Name string // aws or azure or gcp or other...
InstanceID string
}