Files
vuls/models/models.go
2017-08-09 16:15:24 +09:00

1034 lines
25 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 (
"bytes"
"fmt"
"strings"
"time"
"github.com/future-architect/vuls/config"
cvedict "github.com/kotakanbe/go-cve-dictionary/models"
)
// JSONVersion is JSON Version
const JSONVersion = "0.3.0"
// ScanResults is slice of ScanResult.
type ScanResults []ScanResult
//TODO
// // 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
JSONVersion string
Lang string
ServerName string // TOML Section key
Family string
Release string
Container Container
Platform Platform
// Scanned Vulns by SSH scan + CPE + OVAL
ScannedCves VulnInfos
Packages Packages
Errors []string
Optional [][]interface{}
}
// ConvertNvdToModel convert NVD to CveContent
func (r ScanResult) ConvertNvdToModel(cveID string, nvd cvedict.Nvd) *CveContent {
var cpes []Cpe
for _, c := range nvd.Cpes {
cpes = append(cpes, Cpe{CpeName: c.CpeName})
}
var refs []Reference
for _, r := range nvd.References {
refs = append(refs, Reference{
Link: r.Link,
Source: r.Source,
})
}
validVec := true
for _, v := range []string{
nvd.AccessVector,
nvd.AccessComplexity,
nvd.Authentication,
nvd.ConfidentialityImpact,
nvd.IntegrityImpact,
nvd.AvailabilityImpact,
} {
if len(v) == 0 {
validVec = false
}
}
vector := ""
if validVec {
vector = fmt.Sprintf("AV:%s/AC:%s/Au:%s/C:%s/I:%s/A:%s",
string(nvd.AccessVector[0]),
string(nvd.AccessComplexity[0]),
string(nvd.Authentication[0]),
string(nvd.ConfidentialityImpact[0]),
string(nvd.IntegrityImpact[0]),
string(nvd.AvailabilityImpact[0]))
}
//TODO CVSSv3
return &CveContent{
Type: NVD,
CveID: cveID,
Summary: nvd.Summary,
Cvss2Score: nvd.Score,
Cvss2Vector: vector,
Severity: "", // severity is not contained in NVD
SourceLink: "https://nvd.nist.gov/vuln/detail/" + cveID,
Cpes: cpes,
CweID: nvd.CweID,
References: refs,
Published: nvd.PublishedDate,
LastModified: nvd.LastModifiedDate,
}
}
// ConvertJvnToModel convert JVN to CveContent
func (r ScanResult) ConvertJvnToModel(cveID string, jvn cvedict.Jvn) *CveContent {
var cpes []Cpe
for _, c := range jvn.Cpes {
cpes = append(cpes, Cpe{CpeName: c.CpeName})
}
refs := []Reference{}
for _, r := range jvn.References {
refs = append(refs, Reference{
Link: r.Link,
Source: r.Source,
})
}
vector := strings.TrimSuffix(strings.TrimPrefix(jvn.Vector, "("), ")")
return &CveContent{
Type: JVN,
CveID: cveID,
Title: jvn.Title,
Summary: jvn.Summary,
Severity: jvn.Severity,
Cvss2Score: jvn.Score,
Cvss2Vector: vector,
SourceLink: jvn.JvnLink,
Cpes: cpes,
References: refs,
Published: jvn.PublishedDate,
LastModified: jvn.LastModifiedDate,
}
}
// FilterByCvssOver is filter function.
func (r ScanResult) FilterByCvssOver(over float64) ScanResult {
// TODO: Set correct default value
if over == 0 {
over = -1.1
}
// TODO: Filter by ignore cves???
filtered := r.ScannedCves.Find(func(v VulnInfo) bool {
values := v.CveContents.Cvss2Scores()
for _, v := range values {
score := v.Value.Score
if over <= score {
return true
}
}
return false
})
copiedScanResult := r
copiedScanResult.ScannedCves = filtered
return copiedScanResult
}
// 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 container 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(ignoreUnscoreCves bool) string {
var high, medium, low, unknown int
for _, vInfo := range r.ScannedCves {
score := vInfo.CveContents.MaxCvss2Score().Value.Score
if score < 0.1 {
score = vInfo.CveContents.MaxCvss3Score().Value.Score
}
switch {
case 7.0 <= score:
high++
case 4.0 <= score:
medium++
case 0 < score:
low++
default:
unknown++
}
}
if ignoreUnscoreCves {
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)
}
// FormatTextReportHeadedr returns header of text report
func (r ScanResult) FormatTextReportHeadedr() string {
serverInfo := r.ServerInfo()
var buf bytes.Buffer
for i := 0; i < len(serverInfo); i++ {
buf.WriteString("=")
}
return fmt.Sprintf("%s\n%s\n%s\t%s\n",
r.ServerInfo(),
buf.String(),
r.CveSummary(config.Conf.IgnoreUnscoredCves),
r.Packages.FormatUpdatablePacksSummary(),
)
}
// Confidence is a ranking how confident the CVE-ID was deteted correctly
// Score: 0 - 100
type Confidence struct {
Score int
DetectionMethod string
}
func (c Confidence) String() string {
return fmt.Sprintf("%d / %s", c.Score, c.DetectionMethod)
}
const (
// CpeNameMatchStr is a String representation of CpeNameMatch
CpeNameMatchStr = "CpeNameMatch"
// YumUpdateSecurityMatchStr is a String representation of YumUpdateSecurityMatch
YumUpdateSecurityMatchStr = "YumUpdateSecurityMatch"
// 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"
// ChangelogLenientMatchStr is a String representation of ChangelogLenientMatch
ChangelogLenientMatchStr = "ChangelogLenientMatch"
// FailedToGetChangelog is a String representation of FailedToGetChangelog
FailedToGetChangelog = "FailedToGetChangelog"
// FailedToFindVersionInChangelog is a String representation of FailedToFindVersionInChangelog
FailedToFindVersionInChangelog = "FailedToFindVersionInChangelog"
)
var (
// CpeNameMatch is a ranking how confident the CVE-ID was deteted correctly
CpeNameMatch = Confidence{100, CpeNameMatchStr}
// YumUpdateSecurityMatch is a ranking how confident the CVE-ID was deteted correctly
YumUpdateSecurityMatch = Confidence{100, YumUpdateSecurityMatchStr}
// PkgAuditMatch is a ranking how confident the CVE-ID was deteted correctly
PkgAuditMatch = Confidence{100, PkgAuditMatchStr}
// OvalMatch is a ranking how confident the CVE-ID was deteted correctly
OvalMatch = Confidence{100, OvalMatchStr}
// ChangelogExactMatch is a ranking how confident the CVE-ID was deteted correctly
ChangelogExactMatch = Confidence{95, ChangelogExactMatchStr}
// ChangelogLenientMatch is a ranking how confident the CVE-ID was deteted correctly
ChangelogLenientMatch = Confidence{50, ChangelogLenientMatchStr}
)
// VulnInfos is VulnInfo list, getter/setter, sortable methods.
type VulnInfos map[string]VulnInfo
// Find elements that matches the function passed in argument
func (v VulnInfos) Find(f func(VulnInfo) bool) VulnInfos {
filtered := VulnInfos{}
for _, vv := range v {
if f(vv) {
filtered[vv.CveID] = vv
}
}
return filtered
}
// FindScoredVulns return socred vulnerabilities
func (v VulnInfos) FindScoredVulns() VulnInfos {
return v.Find(func(vv VulnInfo) bool {
if 0 < vv.CveContents.MaxCvss2Score().Value.Score ||
0 < vv.CveContents.MaxCvss3Score().Value.Score {
return true
}
return false
})
}
// VulnInfo holds a vulnerability information and unsecure packages
type VulnInfo struct {
CveID string
Confidence Confidence
PackageNames []string
DistroAdvisories []DistroAdvisory // for Aamazon, RHEL, FreeBSD
CpeNames []string
CveContents CveContents
}
// Cvss2CalcURL returns CVSS v2 caluclator's URL
func (v VulnInfo) Cvss2CalcURL() string {
return "https://nvd.nist.gov/vuln-metrics/cvss/v2-calculator?name=" + v.CveID
}
// Cvss3CalcURL returns CVSS v3 caluclator's URL
func (v VulnInfo) Cvss3CalcURL() string {
return "https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?name=" + v.CveID
}
// TODO
// NilToEmpty set nil slice or map fields to empty to avoid null in JSON
// func (v *VulnInfo) NilToEmpty() {
// if v.CpeNames == nil {
// v.CpeNames = []string{}
// }
// if v.DistroAdvisories == nil {
// v.DistroAdvisories = []DistroAdvisory{}
// }
// if v.PackageNames == nil {
// v.PackageNames = []string{}
// }
// if v.CveContents == nil {
// v.CveContents = NewCveContents()
// }
// }
// CveContentType is a source of CVE information
type CveContentType string
// NewCveContentType create CveContentType
func NewCveContentType(name string) CveContentType {
switch name {
case "nvd":
return NVD
case "jvn":
return JVN
case "redhat", "centos":
return RedHat
case "ubuntu":
return Ubuntu
case "debian":
return Debian
default:
return Unknown
}
}
const (
// NVD is NVD
NVD CveContentType = "nvd"
// JVN is JVN
JVN CveContentType = "jvn"
// RedHat is RedHat
RedHat CveContentType = "redhat"
// Debian is Debian
Debian CveContentType = "debian"
// Ubuntu is Ubuntu
Ubuntu CveContentType = "ubuntu"
// Unknown is Unknown
Unknown CveContentType = "unknown"
)
// CveContentTypes has slide of CveContentType
type CveContentTypes []CveContentType
// AllCveContetTypes has all of CveContentTypes
var AllCveContetTypes = CveContentTypes{NVD, JVN, RedHat, Debian, Ubuntu}
// Except returns CveContentTypes except for given args
func (c CveContentTypes) Except(excepts ...CveContentType) (excepted CveContentTypes) {
for _, ctype := range c {
found := false
for _, except := range excepts {
if ctype == except {
found = true
break
}
}
if !found {
excepted = append(excepted, ctype)
}
}
return
}
// CveContents has CveContent
type CveContents map[CveContentType]CveContent
// NewCveContents create CveContents
func NewCveContents(conts ...CveContent) CveContents {
m := map[CveContentType]CveContent{}
for _, cont := range conts {
m[cont.Type] = cont
}
return m
}
// CveContentStr has CveContentType and Value
type CveContentStr struct {
Type CveContentType
Value string
}
// Except returns CveContents except given keys for enumeration
func (v CveContents) Except(exceptCtypes ...CveContentType) (values CveContents) {
for ctype, content := range v {
found := false
for _, exceptCtype := range exceptCtypes {
if ctype == exceptCtype {
found = true
break
}
}
if !found {
values[ctype] = content
}
}
return
}
// CveContentCvss2 has CveContentType and Cvss2
type CveContentCvss2 struct {
Type CveContentType
Value Cvss2
}
// Cvss2 has CVSS v2
type Cvss2 struct {
Score float64
Vector string
Severity string
}
// Format CVSS Score and Vector
func (c Cvss2) Format() string {
return fmt.Sprintf("%3.1f/%s", c.Score, c.Vector)
}
func cvss2ScoreToSeverity(score float64) string {
if 7.0 <= score {
return "HIGH"
} else if 4.0 <= score {
return "MEDIUM"
}
return "LOW"
}
// Cvss2Scores returns CVSS V2 Scores
func (v CveContents) Cvss2Scores() (values []CveContentCvss2) {
order := []CveContentType{NVD, RedHat, JVN}
for _, ctype := range order {
if cont, found := v[ctype]; found && 0 < cont.Cvss2Score {
// https://nvd.nist.gov/vuln-metrics/cvss
sev := cont.Severity
if ctype == NVD {
sev = cvss2ScoreToSeverity(cont.Cvss2Score)
}
values = append(values, CveContentCvss2{
Type: ctype,
Value: Cvss2{
Score: cont.Cvss2Score,
Vector: cont.Cvss2Vector,
Severity: sev,
},
})
}
}
return
}
// MaxCvss2Score returns Max CVSS V2 Score
func (v CveContents) MaxCvss2Score() CveContentCvss2 {
//TODO Severity Ubuntu, Debian...
order := []CveContentType{NVD, RedHat, JVN}
max := 0.0
value := CveContentCvss2{
Type: Unknown,
Value: Cvss2{},
}
for _, ctype := range order {
if cont, found := v[ctype]; found && max < cont.Cvss2Score {
// https://nvd.nist.gov/vuln-metrics/cvss
sev := cont.Severity
if ctype == NVD {
sev = cvss2ScoreToSeverity(cont.Cvss2Score)
}
value = CveContentCvss2{
Type: ctype,
Value: Cvss2{
Score: cont.Cvss2Score,
Vector: cont.Cvss2Vector,
Severity: sev,
},
}
max = cont.Cvss2Score
}
}
return value
}
// CveContentCvss3 has CveContentType and Cvss3
type CveContentCvss3 struct {
Type CveContentType
Value Cvss3
}
// Cvss3 has CVSS v3 Score, Vector and Severity
type Cvss3 struct {
Score float64
Vector string
Severity string
}
// Format CVSS Score and Vector
func (c Cvss3) Format() string {
return fmt.Sprintf("%3.1f/CVSS:3.0/%s", c.Score, c.Vector)
}
func cvss3ScoreToSeverity(score float64) string {
if 9.0 <= score {
return "CRITICAL"
} else if 7.0 <= score {
return "HIGH"
} else if 4.0 <= score {
return "MEDIUM"
}
return "LOW"
}
// Cvss3Scores returns CVSS V3 Score
func (v CveContents) Cvss3Scores() (values []CveContentCvss3) {
//TODO Severity Ubuntu, Debian...
order := []CveContentType{RedHat}
for _, ctype := range order {
if cont, found := v[ctype]; found && 0 < cont.Cvss3Score {
// https://nvd.nist.gov/vuln-metrics/cvss
sev := cont.Severity
if ctype == NVD {
sev = cvss3ScoreToSeverity(cont.Cvss2Score)
}
values = append(values, CveContentCvss3{
Type: ctype,
Value: Cvss3{
Score: cont.Cvss3Score,
Vector: cont.Cvss3Vector,
Severity: sev,
},
})
}
}
return
}
// MaxCvss3Score returns Max CVSS V3 Score
func (v CveContents) MaxCvss3Score() CveContentCvss3 {
//TODO Severity Ubuntu, Debian...
order := []CveContentType{RedHat}
max := 0.0
value := CveContentCvss3{
Type: Unknown,
Value: Cvss3{},
}
for _, ctype := range order {
if cont, found := v[ctype]; found && max < cont.Cvss3Score {
// https://nvd.nist.gov/vuln-metrics/cvss
sev := cont.Severity
if ctype == NVD {
sev = cvss3ScoreToSeverity(cont.Cvss2Score)
}
value = CveContentCvss3{
Type: ctype,
Value: Cvss3{
Score: cont.Cvss3Score,
Vector: cont.Cvss3Vector,
Severity: sev,
},
}
max = cont.Cvss3Score
}
}
return value
}
// FormatMaxCvssScore returns Max CVSS Score
func (v CveContents) FormatMaxCvssScore() string {
v2Max := v.MaxCvss2Score()
v3Max := v.MaxCvss3Score()
if v2Max.Value.Score <= v3Max.Value.Score {
return fmt.Sprintf("%3.1f %s (%s)",
v3Max.Value.Score,
strings.ToUpper(v3Max.Value.Severity),
v3Max.Type)
}
return fmt.Sprintf("%3.1f %s (%s)",
v2Max.Value.Score,
strings.ToUpper(v2Max.Value.Severity),
v2Max.Type)
}
// Titles returns tilte (TUI)
func (v CveContents) Titles(lang, myFamily string) (values []CveContentStr) {
if lang == "ja" {
if cont, found := v[JVN]; found && 0 < len(cont.Title) {
values = append(values, CveContentStr{JVN, cont.Title})
}
}
order := CveContentTypes{NVD, NewCveContentType(myFamily)}
order = append(order, AllCveContetTypes.Except(append(order, JVN)...)...)
for _, ctype := range order {
// Only JVN has meaningful title. so return first 100 char of summary
if cont, found := v[ctype]; found && 0 < len(cont.Summary) {
summary := strings.Replace(cont.Summary, "\n", " ", -1)
index := 75
if len(summary) < index {
index = len(summary)
}
values = append(values, CveContentStr{
Type: ctype,
Value: summary[0:index] + "...",
})
}
}
if len(values) == 0 {
values = []CveContentStr{{
Type: Unknown,
Value: "-",
}}
}
return
}
// Summaries returns summaries
func (v CveContents) Summaries(lang, myFamily string) (values []CveContentStr) {
if lang == "ja" {
if cont, found := v[JVN]; found && 0 < len(cont.Summary) {
summary := cont.Title
summary += "\n" + strings.Replace(
strings.Replace(cont.Summary, "\n", " ", -1), "\r", " ", -1)
values = append(values, CveContentStr{JVN, summary})
}
}
order := CveContentTypes{NVD, NewCveContentType(myFamily)}
order = append(order, AllCveContetTypes.Except(append(order, JVN)...)...)
for _, ctype := range order {
if cont, found := v[ctype]; found && 0 < len(cont.Summary) {
summary := strings.Replace(cont.Summary, "\n", " ", -1)
values = append(values, CveContentStr{
Type: ctype,
Value: summary,
})
}
}
if len(values) == 0 {
values = []CveContentStr{{
Type: Unknown,
Value: "-",
}}
}
return
}
// SourceLinks returns link of source
func (v CveContents) SourceLinks(lang, myFamily, cveID string) (values []CveContentStr) {
if lang == "ja" {
if cont, found := v[JVN]; found && !cont.Empty() {
values = append(values, CveContentStr{JVN, cont.SourceLink})
}
}
order := CveContentTypes{NVD, NewCveContentType(myFamily)}
for _, ctype := range order {
if cont, found := v[ctype]; found {
values = append(values, CveContentStr{ctype, cont.SourceLink})
}
}
if len(values) == 0 {
return []CveContentStr{{
Type: NVD,
Value: "https://nvd.nist.gov/vuln/detail/" + cveID,
}}
}
return values
}
// VendorLink returns link of source
func (v CveContents) VendorLink(myFamily string) CveContentStr {
ctype := NewCveContentType(myFamily)
if cont, ok := v[ctype]; ok {
return CveContentStr{ctype, cont.SourceLink}
}
return CveContentStr{ctype, ""}
}
// Severities returns Severities
// func (v CveContents) Severities(myFamily string) (values []CveContentValue) {
// order := CveContentTypes{NVD, NewCveContentType(myFamily)}
// order = append(order, AllCveContetTypes.Except(append(order)...)...)
// for _, ctype := range order {
// if cont, found := v[ctype]; found && 0 < len(cont.Severity) {
// values = append(values, CveContentValue{
// Type: ctype,
// Value: cont.Severity,
// })
// }
// }
// return
// }
// CveContentCpes has CveContentType and Value
type CveContentCpes struct {
Type CveContentType
Value []Cpe
}
// Cpes returns affected CPEs of this Vulnerability
func (v CveContents) Cpes(myFamily string) (values []CveContentCpes) {
order := CveContentTypes{NewCveContentType(myFamily)}
order = append(order, AllCveContetTypes.Except(append(order)...)...)
for _, ctype := range order {
if cont, found := v[ctype]; found && 0 < len(cont.Cpes) {
values = append(values, CveContentCpes{
Type: ctype,
Value: cont.Cpes,
})
}
}
return
}
// CveContentRefs has CveContentType and Cpes
type CveContentRefs struct {
Type CveContentType
Value []Reference
}
// References returns References
func (v CveContents) References(myFamily string) (values []CveContentRefs) {
order := CveContentTypes{NewCveContentType(myFamily)}
order = append(order, AllCveContetTypes.Except(append(order)...)...)
for _, ctype := range order {
if cont, found := v[ctype]; found && 0 < len(cont.References) {
values = append(values, CveContentRefs{
Type: ctype,
Value: cont.References,
})
}
}
return
}
// CweIDs returns related CweIDs of the vulnerability
func (v CveContents) CweIDs(myFamily string) (values []CveContentStr) {
order := CveContentTypes{NewCveContentType(myFamily)}
order = append(order, AllCveContetTypes.Except(append(order)...)...)
for _, ctype := range order {
if cont, found := v[ctype]; found && 0 < len(cont.CweID) {
// RedHat's OVAL sometimes contains multiple CWE-IDs separated by spaces
for _, cweID := range strings.Fields(cont.CweID) {
values = append(values, CveContentStr{
Type: ctype,
Value: cweID,
})
}
}
}
return
}
// CveContent has abstraction of various vulnerability information
type CveContent struct {
Type CveContentType
CveID string
Title string
Summary string
Severity string
Cvss2Score float64
Cvss2Vector string
Cvss3Score float64
Cvss3Vector string
SourceLink string
Cpes []Cpe
References References
CweID string
Published time.Time
LastModified time.Time
}
// Empty checks the content is empty
func (c CveContent) Empty() bool {
return c.Summary == ""
}
// Cpe is Common Platform Enumeration
type Cpe struct {
CpeName string
}
// References is a slice of Reference
type References []Reference
// Find elements that matches the function passed in argument
func (r References) Find(f func(r Reference) bool) (refs []Reference) {
for _, rr := range r {
refs = append(refs, rr)
}
return
}
// Reference has a related link of the CVE
type Reference struct {
Source string
Link string
RefID string
}
// Packages is Map of Package
// { "package-name": Package }
type Packages map[string]Package
// NewPackages create Packages
func NewPackages(packs ...Package) Packages {
m := Packages{}
for _, pack := range packs {
m[pack.Name] = pack
}
return m
}
// MergeNewVersion merges candidate version information to the receiver struct
func (ps Packages) MergeNewVersion(as Packages) {
for _, a := range as {
if pack, ok := ps[a.Name]; ok {
pack.NewVersion = a.NewVersion
pack.NewRelease = a.NewRelease
ps[a.Name] = pack
}
}
}
// Merge returns merged map (immutable)
func (ps Packages) Merge(other Packages) Packages {
merged := map[string]Package{}
for k, v := range ps {
merged[k] = v
}
for k, v := range other {
merged[k] = v
}
return merged
}
// FormatVersionsFromTo returns updatable packages
func (ps Packages) FormatVersionsFromTo() string {
ss := []string{}
for _, pack := range ps {
ss = append(ss, pack.FormatVersionFromTo())
}
return strings.Join(ss, "\n")
}
// FormatUpdatablePacksSummary returns a summary of updatable packages
func (ps Packages) FormatUpdatablePacksSummary() string {
nUpdatable := 0
for _, p := range ps {
if p.NewVersion != "" {
nUpdatable++
}
}
return fmt.Sprintf("%d updatable packages", nUpdatable)
}
// Package has installed packages.
type Package struct {
Name string
Version string
Release string
NewVersion string
NewRelease string
Repository string
Changelog Changelog
NotFixedYet bool // Ubuntu OVAL Only
}
// FormatVer returns package name-version-release
func (p Package) FormatVer() 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
}
// FormatNewVer returns package name-version-release
func (p Package) FormatNewVer() 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
}
// FormatVersionFromTo formats installed and new package version
func (p Package) FormatVersionFromTo() string {
return fmt.Sprintf("%s -> %s", p.FormatVer(), p.FormatNewVer())
}
// Changelog has contents of changelog and how to get it.
// Method: modesl.detectionMethodStr
type Changelog struct {
Contents string
Method string
}
// 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
Type string
}
// Platform has platform information
type Platform struct {
Name string // aws or azure or gcp or other...
InstanceID string
}