Merge pull request #270 from future-architect/report-subcommand

Add report subcommand, change scan options. #239
This commit is contained in:
Kota Kanbe
2017-01-10 16:15:01 +09:00
committed by GitHub
43 changed files with 2761 additions and 1979 deletions

View File

@@ -26,7 +26,6 @@ import (
"github.com/Sirupsen/logrus"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/cveapi"
"github.com/future-architect/vuls/models"
)
@@ -224,62 +223,16 @@ func (l base) isAwsInstanceID(str string) bool {
}
func (l *base) convertToModel() (models.ScanResult, error) {
var scoredCves, unscoredCves, ignoredCves models.CveInfos
for _, p := range l.UnsecurePackages {
sort.Sort(models.PackageInfosByName(p.Packs))
// ignoreCves
found := false
for _, icve := range l.getServerInfo().IgnoreCves {
if icve == p.CveDetail.CveID {
ignoredCves = append(ignoredCves, models.CveInfo{
CveDetail: p.CveDetail,
Packages: p.Packs,
DistroAdvisories: p.DistroAdvisories,
})
found = true
break
}
}
if found {
continue
}
// unscoredCves
if p.CveDetail.CvssScore(config.Conf.Lang) <= 0 {
unscoredCves = append(unscoredCves, models.CveInfo{
CveDetail: p.CveDetail,
Packages: p.Packs,
DistroAdvisories: p.DistroAdvisories,
})
continue
}
cpenames := []models.CpeName{}
for _, cpename := range p.CpeNames {
cpenames = append(cpenames,
models.CpeName{Name: cpename})
}
// scoredCves
cve := models.CveInfo{
CveDetail: p.CveDetail,
Packages: p.Packs,
DistroAdvisories: p.DistroAdvisories,
CpeNames: cpenames,
}
scoredCves = append(scoredCves, cve)
for _, p := range l.VulnInfos {
sort.Sort(models.PackageInfosByName(p.Packages))
}
sort.Sort(l.VulnInfos)
container := models.Container{
ContainerID: l.ServerInfo.Container.ContainerID,
Name: l.ServerInfo.Container.Name,
}
sort.Sort(scoredCves)
sort.Sort(unscoredCves)
sort.Sort(ignoredCves)
return models.ScanResult{
ServerName: l.ServerInfo.ServerName,
ScannedAt: time.Now(),
@@ -287,52 +240,12 @@ func (l *base) convertToModel() (models.ScanResult, error) {
Release: l.Distro.Release,
Container: container,
Platform: l.Platform,
KnownCves: scoredCves,
UnknownCves: unscoredCves,
IgnoredCves: ignoredCves,
ScannedCves: l.VulnInfos,
Packages: l.Packages,
Optional: l.ServerInfo.Optional,
}, nil
}
// scanVulnByCpeName search vulnerabilities that specified in config file.
func (l *base) scanVulnByCpeName() error {
unsecurePacks := CvePacksList{}
serverInfo := l.getServerInfo()
cpeNames := serverInfo.CpeNames
// remove duplicate
set := map[string]CvePacksInfo{}
for _, name := range cpeNames {
details, err := cveapi.CveClient.FetchCveDetailsByCpeName(name)
if err != nil {
return err
}
for _, detail := range details {
if val, ok := set[detail.CveID]; ok {
names := val.CpeNames
names = append(names, name)
val.CpeNames = names
set[detail.CveID] = val
} else {
set[detail.CveID] = CvePacksInfo{
CveID: detail.CveID,
CveDetail: detail,
CpeNames: []string{name},
}
}
}
}
for key := range set {
unsecurePacks = append(unsecurePacks, set[key])
}
unsecurePacks = append(unsecurePacks, l.UnsecurePackages...)
l.setUnsecurePackages(unsecurePacks)
return nil
}
func (l *base) setErrs(errs []error) {
l.errs = errs
}

View File

@@ -26,7 +26,6 @@ import (
"github.com/future-architect/vuls/cache"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/cveapi"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
)
@@ -179,12 +178,12 @@ func (o *debian) scanPackages() error {
}
o.setPackages(packs)
var unsecurePacks []CvePacksInfo
var unsecurePacks []models.VulnInfo
if unsecurePacks, err = o.scanUnsecurePackages(packs); err != nil {
o.log.Errorf("Failed to scan vulnerable packages")
return err
}
o.setUnsecurePackages(unsecurePacks)
o.setVulnInfos(unsecurePacks)
return nil
}
@@ -242,37 +241,41 @@ func (o *debian) checkRequiredPackagesInstalled() error {
return nil
}
func (o *debian) scanUnsecurePackages(packs []models.PackageInfo) ([]CvePacksInfo, error) {
func (o *debian) scanUnsecurePackages(installed []models.PackageInfo) ([]models.VulnInfo, error) {
o.log.Infof("apt-get update...")
cmd := util.PrependProxyEnv("apt-get update")
if r := o.ssh(cmd, sudo); !r.isSuccess() {
return nil, fmt.Errorf("Failed to SSH: %s", r)
}
upgradablePackNames, err := o.GetUpgradablePackNames()
// Convert the name of upgradable packages to PackageInfo struct
upgradableNames, err := o.GetUpgradablePackNames()
if err != nil {
return []CvePacksInfo{}, err
return nil, err
}
// Convert package name to PackageInfo struct
var unsecurePacks []models.PackageInfo
for _, name := range upgradablePackNames {
for _, pack := range packs {
var upgradablePacks []models.PackageInfo
for _, name := range upgradableNames {
for _, pack := range installed {
if pack.Name == name {
unsecurePacks = append(unsecurePacks, pack)
upgradablePacks = append(upgradablePacks, pack)
break
}
}
}
unsecurePacks, err = o.fillCandidateVersion(unsecurePacks)
// Fill the candidate versions of upgradable packages
upgradablePacks, err = o.fillCandidateVersion(upgradablePacks)
if err != nil {
return nil, fmt.Errorf("Failed to fill candidate versions. err: %s", err)
}
o.Packages.MergeNewVersion(upgradablePacks)
// Setup changelog cache
current := cache.Meta{
Name: o.getServerInfo().GetServerName(),
Distro: o.getServerInfo().Distro,
Packs: unsecurePacks,
Packs: upgradablePacks,
}
o.log.Debugf("Ensure changelog cache: %s", current.Name)
if err := o.ensureChangelogCache(current); err != nil {
@@ -280,12 +283,12 @@ func (o *debian) scanUnsecurePackages(packs []models.PackageInfo) ([]CvePacksInf
}
// Collect CVE information of upgradable packages
cvePacksInfos, err := o.scanPackageCveInfos(unsecurePacks)
vulnInfos, err := o.scanVulnInfos(upgradablePacks)
if err != nil {
return nil, fmt.Errorf("Failed to scan unsecure packages. err: %s", err)
}
return cvePacksInfos, nil
return vulnInfos, nil
}
func (o *debian) ensureChangelogCache(current cache.Meta) error {
@@ -327,7 +330,7 @@ func (o *debian) fillCandidateVersion(before models.PackageInfoList) (filled []m
cmd := fmt.Sprintf("LANGUAGE=en_US.UTF-8 apt-cache policy %s", strings.Join(names, " "))
r := o.ssh(cmd, sudo)
if !r.isSuccess() {
return nil, fmt.Errorf("Failed to SSH: %s.", r)
return nil, fmt.Errorf("Failed to SSH: %s", r)
}
packChangelog := o.splitAptCachePolicy(r.Stdout)
for k, v := range packChangelog {
@@ -398,26 +401,26 @@ func (o *debian) parseAptGetUpgrade(stdout string) (upgradableNames []string, er
return
}
func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePacksList CvePacksList, err error) {
func (o *debian) scanVulnInfos(upgradablePacks []models.PackageInfo) (models.VulnInfos, error) {
meta := cache.Meta{
Name: o.getServerInfo().GetServerName(),
Distro: o.getServerInfo().Distro,
Packs: unsecurePacks,
Packs: upgradablePacks,
}
type strarray []string
resChan := make(chan struct {
models.PackageInfo
strarray
}, len(unsecurePacks))
errChan := make(chan error, len(unsecurePacks))
reqChan := make(chan models.PackageInfo, len(unsecurePacks))
}, len(upgradablePacks))
errChan := make(chan error, len(upgradablePacks))
reqChan := make(chan models.PackageInfo, len(upgradablePacks))
defer close(resChan)
defer close(errChan)
defer close(reqChan)
go func() {
for _, pack := range unsecurePacks {
for _, pack := range upgradablePacks {
reqChan <- pack
}
}()
@@ -425,7 +428,7 @@ func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePac
timeout := time.After(30 * 60 * time.Second)
concurrency := 10
tasks := util.GenWorkers(concurrency)
for range unsecurePacks {
for range upgradablePacks {
tasks <- func() {
select {
case pack := <-reqChan:
@@ -458,7 +461,7 @@ func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePac
// { CVE ID: [packageInfo] }
cvePackages := make(map[string][]models.PackageInfo)
errs := []error{}
for i := 0; i < len(unsecurePacks); i++ {
for i := 0; i < len(upgradablePacks); i++ {
select {
case pair := <-resChan:
pack := pair.PackageInfo
@@ -467,12 +470,11 @@ func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePac
cvePackages[cveID] = appendPackIfMissing(cvePackages[cveID], pack)
}
o.log.Infof("(%d/%d) Scanned %s-%s : %s",
i+1, len(unsecurePacks), pair.Name, pair.PackageInfo.Version, cveIDs)
i+1, len(upgradablePacks), pair.Name, pair.PackageInfo.Version, cveIDs)
case err := <-errChan:
errs = append(errs, err)
case <-timeout:
//TODO append to errs
return nil, fmt.Errorf("Timeout scanPackageCveIDs")
errs = append(errs, fmt.Errorf("Timeout scanPackageCveIDs"))
}
}
if 0 < len(errs) {
@@ -484,23 +486,14 @@ func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePac
cveIDs = append(cveIDs, k)
}
o.log.Debugf("%d Cves are found. cves: %v", len(cveIDs), cveIDs)
o.log.Info("Fetching CVE details...")
cveDetails, err := cveapi.CveClient.FetchCveDetails(cveIDs)
if err != nil {
return nil, err
}
o.log.Info("Done")
for _, detail := range cveDetails {
cvePacksList = append(cvePacksList, CvePacksInfo{
CveID: detail.CveID,
CveDetail: detail,
Packs: cvePackages[detail.CveID],
// CvssScore: cinfo.CvssScore(conf.Lang),
var vinfos models.VulnInfos
for k, v := range cvePackages {
vinfos = append(vinfos, models.VulnInfo{
CveID: k,
Packages: v,
})
}
return
return vinfos, nil
}
func (o *debian) getChangelogCache(meta cache.Meta, pack models.PackageInfo) string {

View File

@@ -22,7 +22,6 @@ import (
"strings"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/cveapi"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
)
@@ -87,12 +86,12 @@ func (o *bsd) scanPackages() error {
}
o.setPackages(packs)
var unsecurePacks []CvePacksInfo
if unsecurePacks, err = o.scanUnsecurePackages(); err != nil {
var vinfos []models.VulnInfo
if vinfos, err = o.scanUnsecurePackages(); err != nil {
o.log.Errorf("Failed to scan vulnerable packages")
return err
}
o.setUnsecurePackages(unsecurePacks)
o.setVulnInfos(vinfos)
return nil
}
@@ -105,7 +104,7 @@ func (o *bsd) scanInstalledPackages() ([]models.PackageInfo, error) {
return o.parsePkgVersion(r.Stdout), nil
}
func (o *bsd) scanUnsecurePackages() (cvePacksList []CvePacksInfo, err error) {
func (o *bsd) scanUnsecurePackages() (vulnInfos []models.VulnInfo, err error) {
const vulndbPath = "/tmp/vuln.db"
cmd := "rm -f " + vulndbPath
r := o.ssh(cmd, noSudo)
@@ -120,7 +119,7 @@ func (o *bsd) scanUnsecurePackages() (cvePacksList []CvePacksInfo, err error) {
}
if r.ExitStatus == 0 {
// no vulnerabilities
return []CvePacksInfo{}, nil
return []models.VulnInfo{}, nil
}
var packAdtRslt []pkgAuditResult
@@ -151,34 +150,22 @@ func (o *bsd) scanUnsecurePackages() (cvePacksList []CvePacksInfo, err error) {
}
}
cveIDs := []string{}
for k := range cveIDAdtMap {
cveIDs = append(cveIDs, k)
}
cveDetails, err := cveapi.CveClient.FetchCveDetails(cveIDs)
if err != nil {
return nil, err
}
o.log.Info("Done")
for _, d := range cveDetails {
packs := []models.PackageInfo{}
for _, r := range cveIDAdtMap[d.CveID] {
for _, r := range cveIDAdtMap[k] {
packs = append(packs, r.pack)
}
disAdvs := []models.DistroAdvisory{}
for _, r := range cveIDAdtMap[d.CveID] {
for _, r := range cveIDAdtMap[k] {
disAdvs = append(disAdvs, models.DistroAdvisory{
AdvisoryID: r.vulnIDCveIDs.vulnID,
})
}
cvePacksList = append(cvePacksList, CvePacksInfo{
CveID: d.CveID,
CveDetail: d,
Packs: packs,
vulnInfos = append(vulnInfos, models.VulnInfo{
CveID: k,
Packages: packs,
DistroAdvisories: disAdvs,
})
}

View File

@@ -26,7 +26,6 @@ import (
"time"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/cveapi"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
@@ -189,12 +188,12 @@ func (o *redhat) scanPackages() error {
}
o.setPackages(packs)
var unsecurePacks []CvePacksInfo
if unsecurePacks, err = o.scanUnsecurePackages(); err != nil {
var vinfos []models.VulnInfo
if vinfos, err = o.scanVulnInfos(); err != nil {
o.log.Errorf("Failed to scan vulnerable packages")
return err
}
o.setUnsecurePackages(unsecurePacks)
o.setVulnInfos(vinfos)
return nil
}
@@ -235,7 +234,7 @@ func (o *redhat) parseScannedPackagesLine(line string) (models.PackageInfo, erro
}, nil
}
func (o *redhat) scanUnsecurePackages() ([]CvePacksInfo, error) {
func (o *redhat) scanVulnInfos() ([]models.VulnInfo, error) {
if o.Distro.Family != "centos" {
// Amazon, RHEL has yum updateinfo as default
// yum updateinfo can collenct vendor advisory information.
@@ -247,7 +246,7 @@ func (o *redhat) scanUnsecurePackages() ([]CvePacksInfo, error) {
}
// For CentOS
func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (CvePacksList, error) {
func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (models.VulnInfos, error) {
cmd := "LANGUAGE=en_US.UTF-8 yum --color=never %s check-update"
if o.getServerInfo().Enablerepo != "" {
cmd = fmt.Sprintf(cmd, "--enablerepo="+o.getServerInfo().Enablerepo)
@@ -268,19 +267,23 @@ func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (CvePacksList, error)
}
o.log.Debugf("%s", pp.Sprintf("%v", packInfoList))
// set candidate version info
o.Packages.MergeNewVersion(packInfoList)
// Collect CVE-IDs in changelog
type PackInfoCveIDs struct {
PackInfo models.PackageInfo
CveIDs []string
}
// { packageName: changelog-lines }
var rpm2changelog map[string]*string
allChangelog, err := o.getAllChangelog(packInfoList)
if err != nil {
o.log.Errorf("Failed to getAllchangelog. err: %s", err)
return nil, err
}
// { packageName: changelog-lines }
var rpm2changelog map[string]*string
rpm2changelog, err = o.parseAllChangelog(allChangelog)
if err != nil {
return nil, fmt.Errorf("Failed to parseAllChangelog. err: %s", err)
@@ -337,39 +340,20 @@ func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (CvePacksList, error)
cveIDPackInfoMap := make(map[string][]models.PackageInfo)
for _, res := range results {
for _, cveID := range res.CveIDs {
// packInfo, found := o.Packages.FindByName(res.Packname)
// if !found {
// return CvePacksList{}, fmt.Errorf(
// "Faild to transform data structure: %v", res.Packname)
// }
cveIDPackInfoMap[cveID] = append(cveIDPackInfoMap[cveID], res.PackInfo)
cveIDPackInfoMap[cveID] = append(
cveIDPackInfoMap[cveID], res.PackInfo)
}
}
var uniqueCveIDs []string
for cveID := range cveIDPackInfoMap {
uniqueCveIDs = append(uniqueCveIDs, cveID)
}
// cveIDs => []cve.CveInfo
o.log.Info("Fetching CVE details...")
cveDetails, err := cveapi.CveClient.FetchCveDetails(uniqueCveIDs)
if err != nil {
return nil, err
}
o.log.Info("Done")
cvePacksList := []CvePacksInfo{}
for _, detail := range cveDetails {
vinfos := []models.VulnInfo{}
for k, v := range cveIDPackInfoMap {
// Amazon, RHEL do not use this method, so VendorAdvisory do not set.
cvePacksList = append(cvePacksList, CvePacksInfo{
CveID: detail.CveID,
CveDetail: detail,
Packs: cveIDPackInfoMap[detail.CveID],
// CvssScore: cinfo.CvssScore(conf.Lang),
vinfos = append(vinfos, models.VulnInfo{
CveID: k,
Packages: v,
})
}
return cvePacksList, nil
return vinfos, nil
}
// parseYumCheckUpdateLines parse yum check-update to get package name, candidate version
@@ -579,11 +563,11 @@ type distroAdvisoryCveIDs struct {
// Scaning unsecure packages using yum-plugin-security.
// Amazon, RHEL
func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (CvePacksList, error) {
func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (models.VulnInfos, error) {
if o.Distro.Family == "centos" {
// CentOS has no security channel.
// So use yum check-update && parse changelog
return CvePacksList{}, fmt.Errorf(
return nil, fmt.Errorf(
"yum updateinfo is not suppported on CentOS")
}
@@ -615,6 +599,9 @@ func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (CvePacksList, err
}
o.log.Debugf("%s", pp.Sprintf("%v", updatable))
// set candidate version info
o.Packages.MergeNewVersion(updatable)
dict := map[string][]models.PackageInfo{}
for _, advIDPackNames := range advIDPackNamesList {
packInfoList := models.PackageInfoList{}
@@ -638,48 +625,41 @@ func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (CvePacksList, err
}
advisoryCveIDsList, err := o.parseYumUpdateinfo(r.Stdout)
if err != nil {
return CvePacksList{}, err
return nil, err
}
// pp.Println(advisoryCveIDsList)
// All information collected.
// Convert to CvePacksList.
// Convert to VulnInfos.
o.log.Info("Fetching CVE details...")
result := CvePacksList{}
vinfos := models.VulnInfos{}
for _, advIDCveIDs := range advisoryCveIDsList {
cveDetails, err :=
cveapi.CveClient.FetchCveDetails(advIDCveIDs.CveIDs)
if err != nil {
return nil, err
}
for _, cveDetail := range cveDetails {
for _, cveID := range advIDCveIDs.CveIDs {
found := false
for i, p := range result {
if cveDetail.CveID == p.CveID {
for i, p := range vinfos {
if cveID == p.CveID {
advAppended := append(p.DistroAdvisories, advIDCveIDs.DistroAdvisory)
result[i].DistroAdvisories = advAppended
vinfos[i].DistroAdvisories = advAppended
packs := dict[advIDCveIDs.DistroAdvisory.AdvisoryID]
result[i].Packs = append(result[i].Packs, packs...)
vinfos[i].Packages = append(vinfos[i].Packages, packs...)
found = true
break
}
}
if !found {
cpinfo := CvePacksInfo{
CveID: cveDetail.CveID,
CveDetail: cveDetail,
cpinfo := models.VulnInfo{
CveID: cveID,
DistroAdvisories: []models.DistroAdvisory{advIDCveIDs.DistroAdvisory},
Packs: dict[advIDCveIDs.DistroAdvisory.AdvisoryID],
Packages: dict[advIDCveIDs.DistroAdvisory.AdvisoryID],
}
result = append(result, cpinfo)
vinfos = append(vinfos, cpinfo)
}
}
}
o.log.Info("Done")
return result, nil
return vinfos, nil
}
var horizontalRulePattern = regexp.MustCompile(`^=+$`)
@@ -929,7 +909,6 @@ func (o *redhat) extractPackNameVerRel(nameVerRel string) (name, ver, rel string
// parseYumUpdateinfoListAvailable collect AdvisorID(RHSA, ALAS), packages
func (o *redhat) parseYumUpdateinfoListAvailable(stdout string) (advisoryIDPacksList, error) {
result := []advisoryIDPacks{}
lines := strings.Split(stdout, "\n")
for _, line := range lines {

View File

@@ -21,6 +21,7 @@ import (
"bufio"
"fmt"
"os"
"path/filepath"
"strings"
"time"
@@ -28,7 +29,7 @@ import (
"github.com/future-architect/vuls/cache"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
cve "github.com/kotakanbe/go-cve-dictionary/models"
"github.com/future-architect/vuls/report"
)
// Log for localhsot
@@ -54,7 +55,6 @@ type osTypeInterface interface {
checkRequiredPackagesInstalled() error
scanPackages() error
scanVulnByCpeName() error
install() error
convertToModel() (models.ScanResult, error)
@@ -72,64 +72,15 @@ type osPackages struct {
Packages models.PackageInfoList
// unsecure packages
UnsecurePackages CvePacksList
VulnInfos models.VulnInfos
}
func (p *osPackages) setPackages(pi models.PackageInfoList) {
p.Packages = pi
}
func (p *osPackages) setUnsecurePackages(pi []CvePacksInfo) {
p.UnsecurePackages = pi
}
// CvePacksList have CvePacksInfo list, getter/setter, sortable methods.
type CvePacksList []CvePacksInfo
// CvePacksInfo hold the CVE information.
type CvePacksInfo struct {
CveID string
CveDetail cve.CveDetail
Packs models.PackageInfoList
DistroAdvisories []models.DistroAdvisory // for Aamazon, RHEL, FreeBSD
CpeNames []string
}
// FindByCveID find by CVEID
func (s CvePacksList) FindByCveID(cveID string) (pi CvePacksInfo, found bool) {
for _, p := range s {
if cveID == p.CveID {
return p, true
}
}
return CvePacksInfo{CveID: cveID}, false
}
// immutable
func (s CvePacksList) set(cveID string, cvePacksInfo CvePacksInfo) CvePacksList {
for i, p := range s {
if cveID == p.CveID {
s[i] = cvePacksInfo
return s
}
}
return append(s, cvePacksInfo)
}
// Len implement Sort Interface
func (s CvePacksList) Len() int {
return len(s)
}
// Swap implement Sort Interface
func (s CvePacksList) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// Less implement Sort Interface
func (s CvePacksList) Less(i, j int) bool {
return s[i].CveDetail.CvssScore(config.Conf.Lang) >
s[j].CveDetail.CvssScore(config.Conf.Lang)
func (p *osPackages) setVulnInfos(vi []models.VulnInfo) {
p.VulnInfos = vi
}
func detectOS(c config.ServerInfo) (osType osTypeInterface) {
@@ -518,14 +469,15 @@ func Scan() []error {
}()
Log.Info("Scanning vulnerable OS packages...")
if errs := scanPackages(); errs != nil {
scannedAt := time.Now()
dir, err := ensureResultDir(scannedAt)
if err != nil {
return []error{err}
}
if errs := scanVulns(dir, scannedAt); errs != nil {
return errs
}
Log.Info("Scanning vulnerable software specified in the CPE...")
if errs := scanVulnByCpeName(); errs != nil {
return errs
}
return nil
}
@@ -553,31 +505,67 @@ func checkRequiredPackagesInstalled() []error {
}, timeoutSec)
}
func scanPackages() []error {
func scanVulns(jsonDir string, scannedAt time.Time) []error {
var results models.ScanResults
timeoutSec := 120 * 60
return parallelSSHExec(func(o osTypeInterface) error {
return o.scanPackages()
}, timeoutSec)
}
// scanVulnByCpeName search vulnerabilities that specified in config file.
func scanVulnByCpeName() []error {
timeoutSec := 30 * 60
return parallelSSHExec(func(o osTypeInterface) error {
return o.scanVulnByCpeName()
}, timeoutSec)
}
// GetScanResults returns Scan Resutls
func GetScanResults() (results models.ScanResults, err error) {
for _, s := range servers {
r, err := s.convertToModel()
if err != nil {
return results, fmt.Errorf("Failed converting to model: %s", err)
errs := parallelSSHExec(func(o osTypeInterface) error {
if err := o.scanPackages(); err != nil {
return err
}
r, err := o.convertToModel()
if err != nil {
return err
}
r.ScannedAt = scannedAt
results = append(results, r)
return nil
}, timeoutSec)
config.Conf.FormatJSON = true
ws := []report.ResultWriter{
report.LocalFileWriter{CurrentDir: jsonDir},
}
return
for _, w := range ws {
if err := w.Write(results...); err != nil {
return []error{
fmt.Errorf("Failed to write summary report: %s", err),
}
}
}
if errs != nil {
return errs
}
report.StdoutWriter{}.WriteScanSummary(results...)
return nil
}
func ensureResultDir(scannedAt time.Time) (currentDir string, err error) {
jsonDirName := scannedAt.Format(time.RFC3339)
resultsDir := config.Conf.ResultsDir
if len(resultsDir) == 0 {
wd, _ := os.Getwd()
resultsDir = filepath.Join(wd, "results")
}
jsonDir := filepath.Join(resultsDir, jsonDirName)
if err := os.MkdirAll(jsonDir, 0700); err != nil {
return "", fmt.Errorf("Failed to create dir: %s", err)
}
symlinkPath := filepath.Join(resultsDir, "current")
if _, err := os.Lstat(symlinkPath); err == nil {
if err := os.Remove(symlinkPath); err != nil {
return "", fmt.Errorf(
"Failed to remove symlink. path: %s, err: %s", symlinkPath, err)
}
}
if err := os.Symlink(jsonDir, symlinkPath); err != nil {
return "", fmt.Errorf(
"Failed to create symlink: path: %s, err: %s", symlinkPath, err)
}
return jsonDir, nil
}

View File

@@ -1,158 +1 @@
package scan
import (
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
cve "github.com/kotakanbe/go-cve-dictionary/models"
"reflect"
"testing"
)
func TestPackageCveInfosSetGet(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 CvePacksList
for _, cid := range test.in {
ps = ps.set(cid, CvePacksInfo{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)
}
}
}
func TestGetScanResults(t *testing.T) {
// setup servers
c := config.ServerInfo{
ServerName: "ubuntu",
}
deb1 := newDebian(c)
deb2 := newDebian(c)
cpis1 := []CvePacksInfo{
{
CveID: "CVE1",
CveDetail: cve.CveDetail{CveID: "CVE1"},
Packs: []models.PackageInfo{
{Name: "mysql-client-5.5"},
{Name: "mysql-server-5.5"},
{Name: "mysql-common-5.5"},
},
},
{
CveID: "CVE2",
CveDetail: cve.CveDetail{CveID: "CVE2"},
Packs: []models.PackageInfo{
{Name: "mysql-common-5.5"},
{Name: "mysql-server-5.5"},
{Name: "mysql-client-5.5"},
},
},
}
cpis2 := []CvePacksInfo{
{
CveID: "CVE3",
CveDetail: cve.CveDetail{CveID: "CVE3"},
Packs: []models.PackageInfo{
{Name: "libcurl3"},
{Name: "curl"},
},
},
{
CveID: "CVE4",
CveDetail: cve.CveDetail{CveID: "CVE4"},
Packs: []models.PackageInfo{
{Name: "bind9"},
{Name: "libdns100"},
},
},
}
deb1.setUnsecurePackages(cpis1)
servers = append(servers, deb1)
deb2.setUnsecurePackages(cpis2)
servers = append(servers, deb2)
// prepare expected data
expectedUnKnownPackages := []map[string][]models.PackageInfo{
{
"CVE1": {
{Name: "mysql-client-5.5"},
{Name: "mysql-common-5.5"},
{Name: "mysql-server-5.5"},
},
},
{
"CVE2": {
{Name: "mysql-client-5.5"},
{Name: "mysql-common-5.5"},
{Name: "mysql-server-5.5"},
},
},
{
"CVE3": {
{Name: "curl"},
{Name: "libcurl3"},
},
},
{
"CVE4": {
{Name: "bind9"},
{Name: "libdns100"},
},
},
}
// check scanResults
scanResults, _ := GetScanResults()
if len(scanResults) != 2 {
t.Errorf("length of scanResults should be 2")
}
for i, result := range scanResults {
if result.ServerName != "ubuntu" {
t.Errorf("expected ubuntu, actual %s", result.ServerName)
}
unKnownCves := result.UnknownCves
if len(unKnownCves) != 2 {
t.Errorf("length of unKnownCves should be 2")
}
for j, unKnownCve := range unKnownCves {
expected := expectedUnKnownPackages[i*2+j][unKnownCve.CveDetail.CveID]
if !reflect.DeepEqual(expected, unKnownCve.Packages) {
t.Errorf("expected %v, actual %v", expected, unKnownCve.Packages)
}
}
}
}