Added FreeBSD support.
This commit is contained in:
254
scan/freebsd.go
Normal file
254
scan/freebsd.go
Normal file
@@ -0,0 +1,254 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"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"
|
||||
)
|
||||
|
||||
// inherit OsTypeInterface
|
||||
type bsd struct {
|
||||
linux
|
||||
}
|
||||
|
||||
// NewBSD constructor
|
||||
func newBsd(c config.ServerInfo) *bsd {
|
||||
d := &bsd{}
|
||||
d.log = util.NewCustomLogger(c)
|
||||
return d
|
||||
}
|
||||
|
||||
//https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/freebsd.rb
|
||||
func detectFreebsd(c config.ServerInfo) (itsMe bool, bsd osTypeInterface) {
|
||||
bsd = newBsd(c)
|
||||
//set sudo option flag
|
||||
c.SudoOpt = config.SudoOption{ExecBySudo: true}
|
||||
bsd.setServerInfo(c)
|
||||
|
||||
if r := sshExec(c, "uname", noSudo); r.isSuccess() {
|
||||
if strings.Contains(r.Stdout, "FreeBSD") == true {
|
||||
if b := sshExec(c, "uname -r", noSudo); b.isSuccess() {
|
||||
bsd.setDistributionInfo("FreeBSD", b.Stdout)
|
||||
} else {
|
||||
return false, bsd
|
||||
}
|
||||
return true, bsd
|
||||
} else {
|
||||
return false, bsd
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
func (o *bsd) install() error {
|
||||
//pkg upgrade
|
||||
cmd := "pkg upgrade"
|
||||
if r := o.ssh(cmd, sudo); !r.isSuccess() {
|
||||
msg := fmt.Sprintf("Failed to %s. status: %d, stdout: %s, stderr: %s",
|
||||
cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
o.log.Errorf(msg)
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *bsd) scanPackages() error {
|
||||
var err error
|
||||
var packs []models.PackageInfo
|
||||
if packs, err = o.scanInstalledPackages(); err != nil {
|
||||
o.log.Errorf("Failed to scan installed packages")
|
||||
return err
|
||||
}
|
||||
o.setPackages(packs)
|
||||
var unsecurePacks []CvePacksInfo
|
||||
if unsecurePacks, err = o.scanUnsecurePackages(packs); err != nil {
|
||||
o.log.Errorf("Failed to scan vulnerable packages")
|
||||
return err
|
||||
}
|
||||
o.setUnsecurePackages(unsecurePacks)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *bsd) scanInstalledPackages() (packs []models.PackageInfo, err error) {
|
||||
//pkg query is a FreeBSD to provide info on a certain package : %n=name %v=version: formatting for string split later
|
||||
r := o.ssh("pkg query '%n*%v'", noSudo)
|
||||
if !r.isSuccess() {
|
||||
return packs, fmt.Errorf(
|
||||
"Failed to scan packages. status: %d, stdout:%s, Stderr: %s",
|
||||
r.ExitStatus, r.Stdout, r.Stderr)
|
||||
}
|
||||
//same format as debain.go
|
||||
lines := strings.Split(r.Stdout, "\n")
|
||||
for _, line := range lines {
|
||||
//for every \n
|
||||
if trimmed := strings.TrimSpace(line); len(trimmed) != 0 {
|
||||
name, version, err := o.parseScanedPackagesLine(trimmed)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("FreeBSD: Failed to parse package")
|
||||
}
|
||||
packs = append(packs, models.PackageInfo{
|
||||
Name: name,
|
||||
Version: version,
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *bsd) parseScanedPackagesLine(line string) (name, version string, err error) {
|
||||
name = strings.Split(line, "*")[0]
|
||||
if len(strings.Split(line, "*")) == 2 {
|
||||
|
||||
version = strings.Split(line, "*")[1]
|
||||
}
|
||||
return name, version, nil
|
||||
}
|
||||
|
||||
func (o *bsd) checkRequiredPackagesInstalled() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *bsd) scanUnsecurePackages(packs []models.PackageInfo) ([]CvePacksInfo, error) {
|
||||
cmd := util.PrependProxyEnv("pkg version -l '>'")
|
||||
r := o.ssh(cmd, noSudo)
|
||||
if !r.isSuccess() {
|
||||
return nil, nil
|
||||
}
|
||||
match := regexp.MustCompile("(.)+[^[-](\\d)]")
|
||||
upgradablePackNames := match.FindAllString(r.Stdout, -1)
|
||||
|
||||
// Convert package name to PackageInfo struct
|
||||
var unsecurePacks []models.PackageInfo
|
||||
var err error
|
||||
for _, name := range upgradablePackNames {
|
||||
for _, pack := range packs {
|
||||
if pack.Name == name {
|
||||
unsecurePacks = append(unsecurePacks, pack)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
/* unsecurePacks, err = o.fillCanidateVersion(unsecurePacks)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to fill canidate versions. err: %s", err)
|
||||
}
|
||||
*/
|
||||
|
||||
// Collect CVE information of upgradable packages
|
||||
cvePacksInfos, err := o.scanPackageCveInfos(unsecurePacks)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to scan unsecure packages. err: %s", err)
|
||||
}
|
||||
|
||||
return cvePacksInfos, nil
|
||||
}
|
||||
|
||||
func (o *bsd) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePacksList CvePacksList, err error) {
|
||||
|
||||
// { CVE ID: [packageInfo] }
|
||||
cvePackages := make(map[string][]models.PackageInfo)
|
||||
|
||||
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))
|
||||
defer close(resChan)
|
||||
defer close(errChan)
|
||||
defer close(reqChan)
|
||||
|
||||
go func() {
|
||||
for _, pack := range unsecurePacks {
|
||||
reqChan <- pack
|
||||
}
|
||||
}()
|
||||
|
||||
timeout := time.After(30 * 60 * time.Second)
|
||||
|
||||
concurrency := 10
|
||||
tasks := util.GenWorkers(concurrency)
|
||||
for range unsecurePacks {
|
||||
tasks <- func() {
|
||||
select {
|
||||
case pack := <-reqChan:
|
||||
func(p models.PackageInfo) {
|
||||
if cveIDs, err := o.scanPackageCveIDs(p); err != nil {
|
||||
errChan <- err
|
||||
} else {
|
||||
resChan <- struct {
|
||||
models.PackageInfo
|
||||
strarray
|
||||
}{p, cveIDs}
|
||||
}
|
||||
}(pack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errs := []error{}
|
||||
for i := 0; i < len(unsecurePacks); i++ {
|
||||
o.log.Info(unsecurePacks[i])
|
||||
select {
|
||||
case pair := <-resChan:
|
||||
pack := pair.PackageInfo
|
||||
cveIDs := pair.strarray
|
||||
for _, cveID := range cveIDs {
|
||||
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)
|
||||
case err := <-errChan:
|
||||
errs = append(errs, err)
|
||||
case <-timeout:
|
||||
return nil, fmt.Errorf("Timeout scanPackageCveIDs")
|
||||
}
|
||||
}
|
||||
|
||||
if 0 < len(errs) {
|
||||
return nil, fmt.Errorf("%v", errs)
|
||||
}
|
||||
|
||||
var cveIDs []string
|
||||
for k := range cvePackages {
|
||||
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),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *bsd) scanPackageCveIDs(pack models.PackageInfo) ([]string, error) {
|
||||
cmd := fmt.Sprintf("pkg audit -F %s | grep CVE-\\w", pack.Name)
|
||||
cmd = util.PrependProxyEnv(cmd)
|
||||
|
||||
r := o.ssh(cmd, noSudo)
|
||||
if !r.isSuccess() {
|
||||
o.log.Warnf("Failed to %s, status: %d, stdout: %s, stderr: %s", cmd, r.ExitStatus, r.Stdout, r.Stderr)
|
||||
return nil, nil
|
||||
}
|
||||
match := regexp.MustCompile("(CVE-\\d{4}-\\d{4})")
|
||||
return match.FindAllString(r.Stdout, -1), nil
|
||||
}
|
||||
Reference in New Issue
Block a user