refactor(config): localize config used like a global variable (#1179)
* refactor(report): LocalFileWriter * refactor -format-json * refacotr: -format-one-email * refactor: -format-csv * refactor: -gzip * refactor: -format-full-text * refactor: -format-one-line-text * refactor: -format-list * refacotr: remove -to-* from config * refactor: IgnoreGitHubDismissed * refactor: GitHub * refactor: IgnoreUnsocred * refactor: diff * refacotr: lang * refacotr: cacheDBPath * refactor: Remove config references * refactor: ScanResults * refacotr: constant pkg * chore: comment * refactor: scanner * refactor: scanner * refactor: serverapi.go * refactor: serverapi * refactor: change pkg structure * refactor: serverapi.go * chore: remove emtpy file * fix(scan): remove -ssh-native-insecure option * fix(scan): remove the deprecated option `keypassword`
This commit is contained in:
30
config/awsconf.go
Normal file
30
config/awsconf.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package config
|
||||
|
||||
// AWSConf is aws config
|
||||
type AWSConf struct {
|
||||
// AWS profile to use
|
||||
Profile string `json:"profile"`
|
||||
|
||||
// AWS region to use
|
||||
Region string `json:"region"`
|
||||
|
||||
// S3 bucket name
|
||||
S3Bucket string `json:"s3Bucket"`
|
||||
|
||||
// /bucket/path/to/results
|
||||
S3ResultsDir string `json:"s3ResultsDir"`
|
||||
|
||||
// The Server-side encryption algorithm used when storing the reports in S3 (e.g., AES256, aws:kms).
|
||||
S3ServerSideEncryption string `json:"s3ServerSideEncryption"`
|
||||
|
||||
Enabled bool `toml:"-" json:"-"`
|
||||
}
|
||||
|
||||
// Validate configuration
|
||||
func (c *AWSConf) Validate() (errs []error) {
|
||||
// TODO
|
||||
if !c.Enabled {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
24
config/azureconf.go
Normal file
24
config/azureconf.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package config
|
||||
|
||||
// AzureConf is azure config
|
||||
type AzureConf struct {
|
||||
// Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified
|
||||
AccountName string `json:"accountName"`
|
||||
|
||||
// Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified
|
||||
AccountKey string `json:"-"`
|
||||
|
||||
// Azure storage container name
|
||||
ContainerName string `json:"containerName"`
|
||||
|
||||
Enabled bool `toml:"-" json:"-"`
|
||||
}
|
||||
|
||||
// Validate configuration
|
||||
func (c *AzureConf) Validate() (errs []error) {
|
||||
// TODO
|
||||
if !c.Enabled {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -9,11 +9,12 @@ import (
|
||||
type ChatWorkConf struct {
|
||||
APIToken string `json:"-"`
|
||||
Room string `json:"-"`
|
||||
Enabled bool `toml:"-" json:"-"`
|
||||
}
|
||||
|
||||
// Validate validates configuration
|
||||
func (c *ChatWorkConf) Validate() (errs []error) {
|
||||
if !Conf.ToChatWork {
|
||||
if !c.Enabled {
|
||||
return
|
||||
}
|
||||
if len(c.Room) == 0 {
|
||||
|
||||
166
config/config.go
166
config/config.go
@@ -3,11 +3,11 @@ package config
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
@@ -23,29 +23,21 @@ var Conf Config
|
||||
|
||||
//Config is struct of Configuration
|
||||
type Config struct {
|
||||
// scan, report
|
||||
Debug bool `json:"debug,omitempty"`
|
||||
DebugSQL bool `json:"debugSQL,omitempty"`
|
||||
Lang string `json:"lang,omitempty"`
|
||||
HTTPProxy string `valid:"url" json:"httpProxy,omitempty"`
|
||||
LogDir string `json:"logDir,omitempty"`
|
||||
ResultsDir string `json:"resultsDir,omitempty"`
|
||||
Pipe bool `json:"pipe,omitempty"`
|
||||
Quiet bool `json:"quiet,omitempty"`
|
||||
NoProgress bool `json:"noProgress,omitempty"`
|
||||
SSHNative bool `json:"sshNative,omitempty"`
|
||||
Vvv bool `json:"vvv,omitempty"`
|
||||
|
||||
Default ServerInfo `json:"default,omitempty"`
|
||||
Servers map[string]ServerInfo `json:"servers,omitempty"`
|
||||
CvssScoreOver float64 `json:"cvssScoreOver,omitempty"`
|
||||
Default ServerInfo `json:"default,omitempty"`
|
||||
Servers map[string]ServerInfo `json:"servers,omitempty"`
|
||||
|
||||
IgnoreUnscoredCves bool `json:"ignoreUnscoredCves,omitempty"`
|
||||
IgnoreUnfixed bool `json:"ignoreUnfixed,omitempty"`
|
||||
IgnoreGitHubDismissed bool `json:"ignore_git_hub_dismissed,omitempty"`
|
||||
|
||||
CacheDBPath string `json:"cacheDBPath,omitempty"`
|
||||
TrivyCacheDBDir string `json:"trivyCacheDBDir,omitempty"`
|
||||
ScanOpts
|
||||
|
||||
// report
|
||||
CveDict GoCveDictConf `json:"cveDict,omitempty"`
|
||||
OvalDict GovalDictConf `json:"ovalDict,omitempty"`
|
||||
Gost GostConf `json:"gost,omitempty"`
|
||||
@@ -60,62 +52,51 @@ type Config struct {
|
||||
Azure AzureConf `json:"-"`
|
||||
ChatWork ChatWorkConf `json:"-"`
|
||||
Telegram TelegramConf `json:"-"`
|
||||
WpScan WpScanConf `json:"-"`
|
||||
Saas SaasConf `json:"-"`
|
||||
|
||||
WpScan WpScanConf `json:"WpScan,omitempty"`
|
||||
ReportOpts
|
||||
}
|
||||
|
||||
Saas SaasConf `json:"-"`
|
||||
DetectIPS bool `json:"detectIps,omitempty"`
|
||||
// ScanOpts is options for scan
|
||||
type ScanOpts struct {
|
||||
Vvv bool `json:"vvv,omitempty"`
|
||||
DetectIPS bool `json:"detectIps,omitempty"`
|
||||
}
|
||||
|
||||
RefreshCve bool `json:"refreshCve,omitempty"`
|
||||
ToSlack bool `json:"toSlack,omitempty"`
|
||||
ToChatWork bool `json:"toChatWork,omitempty"`
|
||||
ToTelegram bool `json:"ToTelegram,omitempty"`
|
||||
ToEmail bool `json:"toEmail,omitempty"`
|
||||
ToSyslog bool `json:"toSyslog,omitempty"`
|
||||
ToLocalFile bool `json:"toLocalFile,omitempty"`
|
||||
ToS3 bool `json:"toS3,omitempty"`
|
||||
ToAzureBlob bool `json:"toAzureBlob,omitempty"`
|
||||
ToHTTP bool `json:"toHTTP,omitempty"`
|
||||
FormatJSON bool `json:"formatJSON,omitempty"`
|
||||
FormatOneEMail bool `json:"formatOneEMail,omitempty"`
|
||||
FormatOneLineText bool `json:"formatOneLineText,omitempty"`
|
||||
FormatList bool `json:"formatList,omitempty"`
|
||||
FormatFullText bool `json:"formatFullText,omitempty"`
|
||||
FormatCsvList bool `json:"formatCsvList,omitempty"`
|
||||
GZIP bool `json:"gzip,omitempty"`
|
||||
DiffPlus bool `json:"diffPlus,omitempty"`
|
||||
DiffMinus bool `json:"diffMinus,omitempty"`
|
||||
Diff bool `json:"diff,omitempty"`
|
||||
// ReportOpts is options for report
|
||||
type ReportOpts struct {
|
||||
// refactored
|
||||
CvssScoreOver float64 `json:"cvssScoreOver,omitempty"`
|
||||
TrivyCacheDBDir string `json:"trivyCacheDBDir,omitempty"`
|
||||
NoProgress bool `json:"noProgress,omitempty"`
|
||||
RefreshCve bool `json:"refreshCve,omitempty"`
|
||||
IgnoreUnfixed bool `json:"ignoreUnfixed,omitempty"`
|
||||
IgnoreUnscoredCves bool `json:"ignoreUnscoredCves,omitempty"`
|
||||
DiffPlus bool `json:"diffPlus,omitempty"`
|
||||
DiffMinus bool `json:"diffMinus,omitempty"`
|
||||
Diff bool `json:"diff,omitempty"`
|
||||
Lang string `json:"lang,omitempty"`
|
||||
|
||||
//TODO move to GitHubConf
|
||||
IgnoreGitHubDismissed bool `json:"ignore_git_hub_dismissed,omitempty"`
|
||||
}
|
||||
|
||||
// ValidateOnConfigtest validates
|
||||
func (c Config) ValidateOnConfigtest() bool {
|
||||
errs := c.checkSSHKeyExist()
|
||||
|
||||
if runtime.GOOS == "windows" && !c.SSHNative {
|
||||
errs = append(errs, xerrors.New("-ssh-native-insecure is needed on windows"))
|
||||
}
|
||||
|
||||
_, err := govalidator.ValidateStruct(c)
|
||||
if err != nil {
|
||||
if _, err := govalidator.ValidateStruct(c); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
for _, err := range errs {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
return len(errs) == 0
|
||||
}
|
||||
|
||||
// ValidateOnScan validates configuration
|
||||
func (c Config) ValidateOnScan() bool {
|
||||
errs := c.checkSSHKeyExist()
|
||||
|
||||
if runtime.GOOS == "windows" && !c.SSHNative {
|
||||
errs = append(errs, xerrors.New("-ssh-native-insecure is needed on windows"))
|
||||
}
|
||||
|
||||
if len(c.ResultsDir) != 0 {
|
||||
if ok, _ := govalidator.IsFilePath(c.ResultsDir); !ok {
|
||||
errs = append(errs, xerrors.Errorf(
|
||||
@@ -123,29 +104,18 @@ func (c Config) ValidateOnScan() bool {
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.CacheDBPath) != 0 {
|
||||
if ok, _ := govalidator.IsFilePath(c.CacheDBPath); !ok {
|
||||
errs = append(errs, xerrors.Errorf(
|
||||
"Cache DB path must be a *Absolute* file path. -cache-dbpath: %s",
|
||||
c.CacheDBPath))
|
||||
}
|
||||
}
|
||||
|
||||
_, err := govalidator.ValidateStruct(c)
|
||||
if err != nil {
|
||||
if _, err := govalidator.ValidateStruct(c); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
for _, err := range errs {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
return len(errs) == 0
|
||||
}
|
||||
|
||||
func (c Config) checkSSHKeyExist() (errs []error) {
|
||||
for serverName, v := range c.Servers {
|
||||
if v.Type == ServerTypePseudo {
|
||||
if v.Type == constant.ServerTypePseudo {
|
||||
continue
|
||||
}
|
||||
if v.KeyPath != "" {
|
||||
@@ -205,28 +175,37 @@ func (c Config) ValidateOnReport() bool {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
if mailerrs := c.EMail.Validate(); 0 < len(mailerrs) {
|
||||
errs = append(errs, mailerrs...)
|
||||
//TODO refactor interface
|
||||
if es := c.EMail.Validate(); 0 < len(es) {
|
||||
errs = append(errs, es...)
|
||||
}
|
||||
|
||||
if slackerrs := c.Slack.Validate(); 0 < len(slackerrs) {
|
||||
errs = append(errs, slackerrs...)
|
||||
if es := c.Slack.Validate(); 0 < len(es) {
|
||||
errs = append(errs, es...)
|
||||
}
|
||||
|
||||
if chatworkerrs := c.ChatWork.Validate(); 0 < len(chatworkerrs) {
|
||||
errs = append(errs, chatworkerrs...)
|
||||
if es := c.ChatWork.Validate(); 0 < len(es) {
|
||||
errs = append(errs, es...)
|
||||
}
|
||||
|
||||
if telegramerrs := c.Telegram.Validate(); 0 < len(telegramerrs) {
|
||||
errs = append(errs, telegramerrs...)
|
||||
if es := c.Telegram.Validate(); 0 < len(es) {
|
||||
errs = append(errs, es...)
|
||||
}
|
||||
|
||||
if syslogerrs := c.Syslog.Validate(); 0 < len(syslogerrs) {
|
||||
errs = append(errs, syslogerrs...)
|
||||
if es := c.Syslog.Validate(); 0 < len(es) {
|
||||
errs = append(errs, es...)
|
||||
}
|
||||
|
||||
if httperrs := c.HTTP.Validate(); 0 < len(httperrs) {
|
||||
errs = append(errs, httperrs...)
|
||||
if es := c.HTTP.Validate(); 0 < len(es) {
|
||||
errs = append(errs, es...)
|
||||
}
|
||||
|
||||
if es := c.AWS.Validate(); 0 < len(es) {
|
||||
errs = append(errs, es...)
|
||||
}
|
||||
|
||||
if es := c.Azure.Validate(); 0 < len(es) {
|
||||
errs = append(errs, es...)
|
||||
}
|
||||
|
||||
for _, err := range errs {
|
||||
@@ -309,36 +288,6 @@ func validateDB(dictionaryDBName, dbType, dbPath, dbURL string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AWSConf is aws config
|
||||
type AWSConf struct {
|
||||
// AWS profile to use
|
||||
Profile string `json:"profile"`
|
||||
|
||||
// AWS region to use
|
||||
Region string `json:"region"`
|
||||
|
||||
// S3 bucket name
|
||||
S3Bucket string `json:"s3Bucket"`
|
||||
|
||||
// /bucket/path/to/results
|
||||
S3ResultsDir string `json:"s3ResultsDir"`
|
||||
|
||||
// The Server-side encryption algorithm used when storing the reports in S3 (e.g., AES256, aws:kms).
|
||||
S3ServerSideEncryption string `json:"s3ServerSideEncryption"`
|
||||
}
|
||||
|
||||
// AzureConf is azure config
|
||||
type AzureConf struct {
|
||||
// Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified
|
||||
AccountName string `json:"accountName"`
|
||||
|
||||
// Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified
|
||||
AccountKey string `json:"-"`
|
||||
|
||||
// Azure storage container name
|
||||
ContainerName string `json:"containerName"`
|
||||
}
|
||||
|
||||
// WpScanConf is wpscan.com config
|
||||
type WpScanConf struct {
|
||||
Token string `toml:"token,omitempty" json:"-"`
|
||||
@@ -354,7 +303,6 @@ type ServerInfo struct {
|
||||
Port string `toml:"port,omitempty" json:"port,omitempty"`
|
||||
SSHConfigPath string `toml:"sshConfigPath,omitempty" json:"sshConfigPath,omitempty"`
|
||||
KeyPath string `toml:"keyPath,omitempty" json:"keyPath,omitempty"`
|
||||
KeyPassword string `json:"-" toml:"-"`
|
||||
CpeNames []string `toml:"cpeNames,omitempty" json:"cpeNames,omitempty"`
|
||||
ScanMode []string `toml:"scanMode,omitempty" json:"scanMode,omitempty"`
|
||||
ScanModules []string `toml:"scanModules,omitempty" json:"scanModules,omitempty"`
|
||||
@@ -377,7 +325,7 @@ type ServerInfo struct {
|
||||
IgnoredJSONKeys []string `toml:"ignoredJSONKeys,omitempty" json:"ignoredJSONKeys,omitempty"`
|
||||
IPv4Addrs []string `toml:"-" json:"ipv4Addrs,omitempty"`
|
||||
IPv6Addrs []string `toml:"-" json:"ipv6Addrs,omitempty"`
|
||||
IPSIdentifiers map[IPS]string `toml:"-" json:"ipsIdentifiers,omitempty"`
|
||||
IPSIdentifiers map[string]string `toml:"-" json:"ipsIdentifiers,omitempty"`
|
||||
WordPress *WordPressConf `toml:"wordpress,omitempty" json:"wordpress,omitempty"`
|
||||
|
||||
// internal use
|
||||
@@ -434,7 +382,7 @@ func (l Distro) String() string {
|
||||
|
||||
// MajorVersion returns Major version
|
||||
func (l Distro) MajorVersion() (int, error) {
|
||||
if l.Family == Amazon {
|
||||
if l.Family == constant.Amazon {
|
||||
if isAmazonLinux1(l.Release) {
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/future-architect/vuls/constant"
|
||||
)
|
||||
|
||||
func TestSyslogConfValidate(t *testing.T) {
|
||||
@@ -55,7 +57,7 @@ func TestSyslogConfValidate(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
Conf.ToSyslog = true
|
||||
tt.conf.Enabled = true
|
||||
errs := tt.conf.Validate()
|
||||
if len(errs) != tt.expectedErrLength {
|
||||
t.Errorf("test: %d, expected %d, actual %d", i, tt.expectedErrLength, len(errs))
|
||||
|
||||
@@ -54,7 +54,7 @@ func (cnf *ExploitConf) Init() {
|
||||
|
||||
// IsFetchViaHTTP returns wether fetch via http
|
||||
func (cnf *ExploitConf) IsFetchViaHTTP() bool {
|
||||
return Conf.Exploit.Type == "http"
|
||||
return cnf.Type == "http"
|
||||
}
|
||||
|
||||
// CheckHTTPHealth do health check
|
||||
|
||||
@@ -54,7 +54,7 @@ func (cnf *GoCveDictConf) Init() {
|
||||
|
||||
// IsFetchViaHTTP returns wether fetch via http
|
||||
func (cnf *GoCveDictConf) IsFetchViaHTTP() bool {
|
||||
return Conf.CveDict.Type == "http"
|
||||
return cnf.Type == "http"
|
||||
}
|
||||
|
||||
// CheckHTTPHealth checks http server status
|
||||
|
||||
@@ -54,7 +54,7 @@ func (cnf *GostConf) Init() {
|
||||
|
||||
// IsFetchViaHTTP returns wether fetch via http
|
||||
func (cnf *GostConf) IsFetchViaHTTP() bool {
|
||||
return Conf.Gost.Type == "http"
|
||||
return cnf.Type == "http"
|
||||
}
|
||||
|
||||
// CheckHTTPHealth do health check
|
||||
|
||||
@@ -55,7 +55,7 @@ func (cnf *GovalDictConf) Init() {
|
||||
|
||||
// IsFetchViaHTTP returns wether fetch via http
|
||||
func (cnf *GovalDictConf) IsFetchViaHTTP() bool {
|
||||
return Conf.OvalDict.Type == "http"
|
||||
return cnf.Type == "http"
|
||||
}
|
||||
|
||||
// CheckHTTPHealth do health check
|
||||
|
||||
@@ -8,12 +8,13 @@ import (
|
||||
|
||||
// HTTPConf is HTTP config
|
||||
type HTTPConf struct {
|
||||
URL string `valid:"url" json:"-"`
|
||||
URL string `valid:"url" json:"-"`
|
||||
Enabled bool `toml:"-" json:"-"`
|
||||
}
|
||||
|
||||
// Validate validates configuration
|
||||
func (c *HTTPConf) Validate() (errs []error) {
|
||||
if !Conf.ToHTTP {
|
||||
if !c.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -28,11 +29,11 @@ const httpKey = "VULS_HTTP_URL"
|
||||
// Init set options with the following priority.
|
||||
// 1. Environment variable
|
||||
// 2. config.toml
|
||||
func (c *HTTPConf) Init(toml HTTPConf) {
|
||||
func (c *HTTPConf) Init(url string) {
|
||||
if os.Getenv(httpKey) != "" {
|
||||
c.URL = os.Getenv(httpKey)
|
||||
}
|
||||
if toml.URL != "" {
|
||||
c.URL = toml.URL
|
||||
if url != "" {
|
||||
c.URL = url
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package config
|
||||
|
||||
// IPS is
|
||||
type IPS string
|
||||
|
||||
const (
|
||||
// DeepSecurity is
|
||||
DeepSecurity IPS = "deepsecurity"
|
||||
DeepSecurity string = "deepsecurity"
|
||||
)
|
||||
|
||||
@@ -53,7 +53,7 @@ func (cnf *MetasploitConf) Init() {
|
||||
|
||||
// IsFetchViaHTTP returns wether fetch via http
|
||||
func (cnf *MetasploitConf) IsFetchViaHTTP() bool {
|
||||
return Conf.Metasploit.Type == "http"
|
||||
return cnf.Type == "http"
|
||||
}
|
||||
|
||||
// CheckHTTPHealth do health check
|
||||
|
||||
73
config/os.go
73
config/os.go
@@ -4,59 +4,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// RedHat is
|
||||
RedHat = "redhat"
|
||||
|
||||
// Debian is
|
||||
Debian = "debian"
|
||||
|
||||
// Ubuntu is
|
||||
Ubuntu = "ubuntu"
|
||||
|
||||
// CentOS is
|
||||
CentOS = "centos"
|
||||
|
||||
// Fedora is
|
||||
// Fedora = "fedora"
|
||||
|
||||
// Amazon is
|
||||
Amazon = "amazon"
|
||||
|
||||
// Oracle is
|
||||
Oracle = "oracle"
|
||||
|
||||
// FreeBSD is
|
||||
FreeBSD = "freebsd"
|
||||
|
||||
// Raspbian is
|
||||
Raspbian = "raspbian"
|
||||
|
||||
// Windows is
|
||||
Windows = "windows"
|
||||
|
||||
// OpenSUSE is
|
||||
OpenSUSE = "opensuse"
|
||||
|
||||
// OpenSUSELeap is
|
||||
OpenSUSELeap = "opensuse.leap"
|
||||
|
||||
// SUSEEnterpriseServer is
|
||||
SUSEEnterpriseServer = "suse.linux.enterprise.server"
|
||||
|
||||
// SUSEEnterpriseDesktop is
|
||||
SUSEEnterpriseDesktop = "suse.linux.enterprise.desktop"
|
||||
|
||||
// SUSEOpenstackCloud is
|
||||
SUSEOpenstackCloud = "suse.openstack.cloud"
|
||||
|
||||
// Alpine is
|
||||
Alpine = "alpine"
|
||||
|
||||
// ServerTypePseudo is used for ServerInfo.Type, r.Family
|
||||
ServerTypePseudo = "pseudo"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
)
|
||||
|
||||
// EOL has End-of-Life information
|
||||
@@ -89,7 +38,7 @@ func (e EOL) IsExtendedSuppportEnded(now time.Time) bool {
|
||||
// https://github.com/aquasecurity/trivy/blob/master/pkg/detector/ospkg/redhat/redhat.go#L20
|
||||
func GetEOL(family, release string) (eol EOL, found bool) {
|
||||
switch family {
|
||||
case Amazon:
|
||||
case constant.Amazon:
|
||||
rel := "2"
|
||||
if isAmazonLinux1(release) {
|
||||
rel = "1"
|
||||
@@ -98,7 +47,7 @@ func GetEOL(family, release string) (eol EOL, found bool) {
|
||||
"1": {StandardSupportUntil: time.Date(2023, 6, 30, 23, 59, 59, 0, time.UTC)},
|
||||
"2": {},
|
||||
}[rel]
|
||||
case RedHat:
|
||||
case constant.RedHat:
|
||||
// https://access.redhat.com/support/policy/updates/errata
|
||||
eol, found = map[string]EOL{
|
||||
"3": {Ended: true},
|
||||
@@ -115,7 +64,7 @@ func GetEOL(family, release string) (eol EOL, found bool) {
|
||||
StandardSupportUntil: time.Date(2029, 5, 31, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
}[major(release)]
|
||||
case CentOS:
|
||||
case constant.CentOS:
|
||||
// https://en.wikipedia.org/wiki/CentOS#End-of-support_schedule
|
||||
// TODO Stream
|
||||
eol, found = map[string]EOL{
|
||||
@@ -126,7 +75,7 @@ func GetEOL(family, release string) (eol EOL, found bool) {
|
||||
"7": {StandardSupportUntil: time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC)},
|
||||
"8": {StandardSupportUntil: time.Date(2021, 12, 31, 23, 59, 59, 0, time.UTC)},
|
||||
}[major(release)]
|
||||
case Oracle:
|
||||
case constant.Oracle:
|
||||
eol, found = map[string]EOL{
|
||||
// Source:
|
||||
// https://www.oracle.com/a/ocom/docs/elsp-lifetime-069338.pdf
|
||||
@@ -145,7 +94,7 @@ func GetEOL(family, release string) (eol EOL, found bool) {
|
||||
StandardSupportUntil: time.Date(2029, 7, 1, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
}[major(release)]
|
||||
case Debian:
|
||||
case constant.Debian:
|
||||
eol, found = map[string]EOL{
|
||||
// https://wiki.debian.org/LTS
|
||||
"6": {Ended: true},
|
||||
@@ -154,10 +103,10 @@ func GetEOL(family, release string) (eol EOL, found bool) {
|
||||
"9": {StandardSupportUntil: time.Date(2022, 6, 30, 23, 59, 59, 0, time.UTC)},
|
||||
"10": {StandardSupportUntil: time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC)},
|
||||
}[major(release)]
|
||||
case Raspbian:
|
||||
case constant.Raspbian:
|
||||
// Not found
|
||||
eol, found = map[string]EOL{}[major(release)]
|
||||
case Ubuntu:
|
||||
case constant.Ubuntu:
|
||||
// https://wiki.ubuntu.com/Releases
|
||||
eol, found = map[string]EOL{
|
||||
"14.10": {Ended: true},
|
||||
@@ -189,9 +138,9 @@ func GetEOL(family, release string) (eol EOL, found bool) {
|
||||
StandardSupportUntil: time.Date(2022, 7, 1, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
}[release]
|
||||
case SUSEEnterpriseServer:
|
||||
case constant.SUSEEnterpriseServer:
|
||||
//TODO
|
||||
case Alpine:
|
||||
case constant.Alpine:
|
||||
// https://github.com/aquasecurity/trivy/blob/master/pkg/detector/ospkg/alpine/alpine.go#L19
|
||||
// https://wiki.alpinelinux.org/wiki/Alpine_Linux:Releases
|
||||
eol, found = map[string]EOL{
|
||||
@@ -218,7 +167,7 @@ func GetEOL(family, release string) (eol EOL, found bool) {
|
||||
"3.12": {StandardSupportUntil: time.Date(2022, 5, 1, 23, 59, 59, 0, time.UTC)},
|
||||
"3.13": {StandardSupportUntil: time.Date(2022, 11, 1, 23, 59, 59, 0, time.UTC)},
|
||||
}[majorDotMinor(release)]
|
||||
case FreeBSD:
|
||||
case constant.FreeBSD:
|
||||
// https://www.freebsd.org/security/
|
||||
eol, found = map[string]EOL{
|
||||
"7": {Ended: true},
|
||||
|
||||
@@ -3,6 +3,8 @@ package config
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/future-architect/vuls/constant"
|
||||
)
|
||||
|
||||
func TestEOL_IsStandardSupportEnded(t *testing.T) {
|
||||
|
||||
@@ -16,11 +16,12 @@ type SlackConf struct {
|
||||
AuthUser string `json:"-" toml:"authUser,omitempty"`
|
||||
NotifyUsers []string `toml:"notifyUsers,omitempty" json:"-"`
|
||||
Text string `json:"-"`
|
||||
Enabled bool `toml:"-" json:"-"`
|
||||
}
|
||||
|
||||
// Validate validates configuration
|
||||
func (c *SlackConf) Validate() (errs []error) {
|
||||
if !Conf.ToSlack {
|
||||
if !c.Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ type SMTPConf struct {
|
||||
To []string `toml:"to,omitempty" json:"-"`
|
||||
Cc []string `toml:"cc,omitempty" json:"-"`
|
||||
SubjectPrefix string `toml:"subjectPrefix,omitempty" json:"-"`
|
||||
Enabled bool `toml:"-" json:"-"`
|
||||
}
|
||||
|
||||
func checkEmails(emails []string) (errs []error) {
|
||||
@@ -31,10 +32,9 @@ func checkEmails(emails []string) (errs []error) {
|
||||
|
||||
// Validate SMTP configuration
|
||||
func (c *SMTPConf) Validate() (errs []error) {
|
||||
if !Conf.ToEmail {
|
||||
if !c.Enabled {
|
||||
return
|
||||
}
|
||||
// Check Emails fromat
|
||||
emails := []string{}
|
||||
emails = append(emails, c.From)
|
||||
emails = append(emails, c.To...)
|
||||
@@ -44,10 +44,10 @@ func (c *SMTPConf) Validate() (errs []error) {
|
||||
errs = append(errs, emailErrs...)
|
||||
}
|
||||
|
||||
if len(c.SMTPAddr) == 0 {
|
||||
if c.SMTPAddr == "" {
|
||||
errs = append(errs, xerrors.New("email.smtpAddr must not be empty"))
|
||||
}
|
||||
if len(c.SMTPPort) == 0 {
|
||||
if c.SMTPPort == "" {
|
||||
errs = append(errs, xerrors.New("email.smtpPort must not be empty"))
|
||||
}
|
||||
if len(c.To) == 0 {
|
||||
|
||||
@@ -17,11 +17,12 @@ type SyslogConf struct {
|
||||
Facility string `json:"-"`
|
||||
Tag string `json:"-"`
|
||||
Verbose bool `json:"-"`
|
||||
Enabled bool `toml:"-" json:"-"`
|
||||
}
|
||||
|
||||
// Validate validates configuration
|
||||
func (c *SyslogConf) Validate() (errs []error) {
|
||||
if !Conf.ToSyslog {
|
||||
if !c.Enabled {
|
||||
return nil
|
||||
}
|
||||
// If protocol is empty, it will connect to the local syslog server.
|
||||
|
||||
@@ -7,13 +7,14 @@ import (
|
||||
|
||||
// TelegramConf is Telegram config
|
||||
type TelegramConf struct {
|
||||
Token string `json:"-"`
|
||||
ChatID string `json:"-"`
|
||||
Token string `json:"-"`
|
||||
ChatID string `json:"-"`
|
||||
Enabled bool `toml:"-" json:"-"`
|
||||
}
|
||||
|
||||
// Validate validates configuration
|
||||
func (c *TelegramConf) Validate() (errs []error) {
|
||||
if !Conf.ToTelegram {
|
||||
if !c.Enabled {
|
||||
return
|
||||
}
|
||||
if len(c.ChatID) == 0 {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/knqyf263/go-cpe/naming"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
@@ -18,10 +19,6 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error {
|
||||
if _, err := toml.DecodeFile(pathToToml, &Conf); err != nil {
|
||||
return err
|
||||
}
|
||||
if keyPass != "" {
|
||||
Conf.Default.KeyPassword = keyPass
|
||||
}
|
||||
|
||||
Conf.CveDict.Init()
|
||||
Conf.OvalDict.Init()
|
||||
Conf.Gost.Init()
|
||||
@@ -31,10 +28,6 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error {
|
||||
index := 0
|
||||
for name, server := range Conf.Servers {
|
||||
server.ServerName = name
|
||||
if 0 < len(server.KeyPassword) {
|
||||
return xerrors.Errorf("[Deprecated] KEYPASSWORD IN CONFIG FILE ARE UNSECURE. REMOVE THEM IMMEDIATELY FOR A SECURITY REASONS. THEY WILL BE REMOVED IN A FUTURE RELEASE: %s", name)
|
||||
}
|
||||
|
||||
if err := setDefaultIfEmpty(&server, Conf.Default); err != nil {
|
||||
return xerrors.Errorf("Failed to set default value to config. server: %s, err: %w", name, err)
|
||||
}
|
||||
@@ -135,7 +128,7 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error {
|
||||
}
|
||||
|
||||
func setDefaultIfEmpty(server *ServerInfo, d ServerInfo) error {
|
||||
if server.Type != ServerTypePseudo {
|
||||
if server.Type != constant.ServerTypePseudo {
|
||||
if len(server.Host) == 0 {
|
||||
return xerrors.Errorf("server.host is empty")
|
||||
}
|
||||
@@ -166,10 +159,6 @@ func setDefaultIfEmpty(server *ServerInfo, d ServerInfo) error {
|
||||
if server.KeyPath == "" {
|
||||
server.KeyPath = Conf.Default.KeyPath
|
||||
}
|
||||
|
||||
if server.KeyPassword == "" {
|
||||
server.KeyPassword = Conf.Default.KeyPassword
|
||||
}
|
||||
}
|
||||
|
||||
if len(server.Lockfiles) == 0 {
|
||||
|
||||
58
constant/constant.go
Normal file
58
constant/constant.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package constant
|
||||
|
||||
// Global constant
|
||||
// Pkg local constants should not be defined here.
|
||||
// Define them in the each package.
|
||||
|
||||
const (
|
||||
// RedHat is
|
||||
RedHat = "redhat"
|
||||
|
||||
// Debian is
|
||||
Debian = "debian"
|
||||
|
||||
// Ubuntu is
|
||||
Ubuntu = "ubuntu"
|
||||
|
||||
// CentOS is
|
||||
CentOS = "centos"
|
||||
|
||||
// Fedora is
|
||||
// Fedora = "fedora"
|
||||
|
||||
// Amazon is
|
||||
Amazon = "amazon"
|
||||
|
||||
// Oracle is
|
||||
Oracle = "oracle"
|
||||
|
||||
// FreeBSD is
|
||||
FreeBSD = "freebsd"
|
||||
|
||||
// Raspbian is
|
||||
Raspbian = "raspbian"
|
||||
|
||||
// Windows is
|
||||
Windows = "windows"
|
||||
|
||||
// OpenSUSE is
|
||||
OpenSUSE = "opensuse"
|
||||
|
||||
// OpenSUSELeap is
|
||||
OpenSUSELeap = "opensuse.leap"
|
||||
|
||||
// SUSEEnterpriseServer is
|
||||
SUSEEnterpriseServer = "suse.linux.enterprise.server"
|
||||
|
||||
// SUSEEnterpriseDesktop is
|
||||
SUSEEnterpriseDesktop = "suse.linux.enterprise.desktop"
|
||||
|
||||
// SUSEOpenstackCloud is
|
||||
SUSEOpenstackCloud = "suse.openstack.cloud"
|
||||
|
||||
// Alpine is
|
||||
Alpine = "alpine"
|
||||
|
||||
// ServerTypePseudo is used for ServerInfo.Type, r.Family
|
||||
ServerTypePseudo = "pseudo"
|
||||
)
|
||||
@@ -1,6 +1,6 @@
|
||||
// +build !scanner
|
||||
|
||||
package report
|
||||
package detector
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -1,6 +1,6 @@
|
||||
// +build !scanner
|
||||
|
||||
package report
|
||||
package detector
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -91,7 +91,7 @@ func NewDBClient(cnf DBClientConf) (dbclient *DBClient, locked bool, err error)
|
||||
|
||||
// NewCveDB returns cve db client
|
||||
func NewCveDB(cnf DBClientConf) (driver cvedb.DB, locked bool, err error) {
|
||||
if config.Conf.CveDict.IsFetchViaHTTP() {
|
||||
if cnf.CveDictCnf.IsFetchViaHTTP() {
|
||||
return nil, false, nil
|
||||
}
|
||||
util.Log.Debugf("open cve-dictionary db (%s)", cnf.CveDictCnf.Type)
|
||||
@@ -115,7 +115,7 @@ func NewCveDB(cnf DBClientConf) (driver cvedb.DB, locked bool, err error) {
|
||||
|
||||
// NewOvalDB returns oval db client
|
||||
func NewOvalDB(cnf DBClientConf) (driver ovaldb.DB, locked bool, err error) {
|
||||
if config.Conf.OvalDict.IsFetchViaHTTP() {
|
||||
if cnf.OvalDictCnf.IsFetchViaHTTP() {
|
||||
return nil, false, nil
|
||||
}
|
||||
path := cnf.OvalDictCnf.URL
|
||||
@@ -142,7 +142,7 @@ func NewOvalDB(cnf DBClientConf) (driver ovaldb.DB, locked bool, err error) {
|
||||
|
||||
// NewGostDB returns db client for Gost
|
||||
func NewGostDB(cnf DBClientConf) (driver gostdb.DB, locked bool, err error) {
|
||||
if config.Conf.Gost.IsFetchViaHTTP() {
|
||||
if cnf.GostCnf.IsFetchViaHTTP() {
|
||||
return nil, false, nil
|
||||
}
|
||||
path := cnf.GostCnf.URL
|
||||
@@ -168,7 +168,7 @@ func NewGostDB(cnf DBClientConf) (driver gostdb.DB, locked bool, err error) {
|
||||
|
||||
// NewExploitDB returns db client for Exploit
|
||||
func NewExploitDB(cnf DBClientConf) (driver exploitdb.DB, locked bool, err error) {
|
||||
if config.Conf.Exploit.IsFetchViaHTTP() {
|
||||
if cnf.ExploitCnf.IsFetchViaHTTP() {
|
||||
return nil, false, nil
|
||||
}
|
||||
path := cnf.ExploitCnf.URL
|
||||
@@ -194,7 +194,7 @@ func NewExploitDB(cnf DBClientConf) (driver exploitdb.DB, locked bool, err error
|
||||
|
||||
// NewMetasploitDB returns db client for Metasploit
|
||||
func NewMetasploitDB(cnf DBClientConf) (driver metasploitdb.DB, locked bool, err error) {
|
||||
if config.Conf.Metasploit.IsFetchViaHTTP() {
|
||||
if cnf.MetasploitCnf.IsFetchViaHTTP() {
|
||||
return nil, false, nil
|
||||
}
|
||||
path := cnf.MetasploitCnf.URL
|
||||
@@ -1,6 +1,6 @@
|
||||
// +build !scanner
|
||||
|
||||
package report
|
||||
package detector
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -9,17 +9,16 @@ import (
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
c "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/contrib/owasp-dependency-check/parser"
|
||||
"github.com/future-architect/vuls/cwe"
|
||||
"github.com/future-architect/vuls/exploit"
|
||||
"github.com/future-architect/vuls/github"
|
||||
"github.com/future-architect/vuls/gost"
|
||||
"github.com/future-architect/vuls/libmanager"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/msf"
|
||||
"github.com/future-architect/vuls/oval"
|
||||
"github.com/future-architect/vuls/reporter"
|
||||
"github.com/future-architect/vuls/util"
|
||||
"github.com/future-architect/vuls/wordpress"
|
||||
gostdb "github.com/knqyf263/gost/db"
|
||||
cvedb "github.com/kotakanbe/go-cve-dictionary/db"
|
||||
cvemodels "github.com/kotakanbe/go-cve-dictionary/models"
|
||||
@@ -29,8 +28,12 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// FillCveInfos fills CVE Detailed Information
|
||||
func FillCveInfos(dbclient DBClient, rs []models.ScanResult, dir string) ([]models.ScanResult, error) {
|
||||
// type Detector struct {
|
||||
// Targets map[string]config.ServerInfo
|
||||
// }
|
||||
|
||||
// Detect vulns and fill CVE detailed information
|
||||
func Detect(dbclient DBClient, rs []models.ScanResult, dir string) ([]models.ScanResult, error) {
|
||||
|
||||
// Use the same reportedAt for all rs
|
||||
reportedAt := time.Now()
|
||||
@@ -74,7 +77,7 @@ func FillCveInfos(dbclient DBClient, rs []models.ScanResult, dir string) ([]mode
|
||||
}
|
||||
}
|
||||
|
||||
if err := libmanager.DetectLibsCves(&r); err != nil {
|
||||
if err := DetectLibsCves(&r, c.Conf.TrivyCacheDBDir, c.Conf.NoProgress); err != nil {
|
||||
return nil, xerrors.Errorf("Failed to fill with Library dependency: %w", err)
|
||||
}
|
||||
|
||||
@@ -87,7 +90,7 @@ func FillCveInfos(dbclient DBClient, rs []models.ScanResult, dir string) ([]mode
|
||||
}
|
||||
|
||||
repos := c.Conf.Servers[r.ServerName].GitHubRepos
|
||||
if err := DetectGitHubCves(&r, repos); err != nil {
|
||||
if err := DetectGitHubCves(&r, repos, c.Conf.IgnoreGitHubDismissed); err != nil {
|
||||
return nil, xerrors.Errorf("Failed to detect GitHub Cves: %w", err)
|
||||
}
|
||||
|
||||
@@ -116,7 +119,8 @@ func FillCveInfos(dbclient DBClient, rs []models.ScanResult, dir string) ([]mode
|
||||
if s, ok := c.Conf.Servers[r.ServerName]; ok {
|
||||
r = r.ClearFields(s.IgnoredJSONKeys)
|
||||
}
|
||||
if err := overwriteJSONFile(dir, r); err != nil {
|
||||
//TODO don't call here
|
||||
if err := reporter.OverwriteJSONFile(dir, r); err != nil {
|
||||
return nil, xerrors.Errorf("Failed to write JSON: %w", err)
|
||||
}
|
||||
}
|
||||
@@ -131,13 +135,32 @@ func FillCveInfos(dbclient DBClient, rs []models.ScanResult, dir string) ([]mode
|
||||
|
||||
for i, r := range rs {
|
||||
r = r.FilterByCvssOver(c.Conf.CvssScoreOver)
|
||||
r = r.FilterIgnoreCves()
|
||||
r = r.FilterUnfixed(c.Conf.IgnoreUnfixed)
|
||||
r = r.FilterIgnorePkgs()
|
||||
r = r.FilterInactiveWordPressLibs(c.Conf.WpScan.DetectInactive)
|
||||
|
||||
// IgnoreCves
|
||||
ignoreCves := []string{}
|
||||
if r.Container.Name == "" {
|
||||
ignoreCves = c.Conf.Servers[r.ServerName].IgnoreCves
|
||||
} else if con, ok := c.Conf.Servers[r.ServerName].Containers[r.Container.Name]; ok {
|
||||
ignoreCves = con.IgnoreCves
|
||||
}
|
||||
r = r.FilterIgnoreCves(ignoreCves)
|
||||
|
||||
// ignorePkgs
|
||||
ignorePkgsRegexps := []string{}
|
||||
if r.Container.Name == "" {
|
||||
ignorePkgsRegexps = config.Conf.Servers[r.ServerName].IgnorePkgsRegexp
|
||||
} else if s, ok := config.Conf.Servers[r.ServerName].Containers[r.Container.Name]; ok {
|
||||
ignorePkgsRegexps = s.IgnorePkgsRegexp
|
||||
}
|
||||
r = r.FilterIgnorePkgs(ignorePkgsRegexps)
|
||||
|
||||
// IgnoreUnscored
|
||||
if c.Conf.IgnoreUnscoredCves {
|
||||
r.ScannedCves = r.ScannedCves.FindScoredVulns()
|
||||
}
|
||||
|
||||
rs[i] = r
|
||||
}
|
||||
return rs, nil
|
||||
@@ -158,7 +181,7 @@ func DetectPkgCves(dbclient DBClient, r *models.ScanResult) error {
|
||||
}
|
||||
} else if reuseScannedCves(r) {
|
||||
util.Log.Infof("r.Release is empty. Use CVEs as it as.")
|
||||
} else if r.Family == c.ServerTypePseudo {
|
||||
} else if r.Family == constant.ServerTypePseudo {
|
||||
util.Log.Infof("pseudo type. Skip OVAL and gost detection")
|
||||
} else {
|
||||
return xerrors.Errorf("Failed to fill CVEs. r.Release is empty")
|
||||
@@ -195,7 +218,7 @@ func DetectPkgCves(dbclient DBClient, r *models.ScanResult) error {
|
||||
}
|
||||
|
||||
// DetectGitHubCves fetches CVEs from GitHub Security Alerts
|
||||
func DetectGitHubCves(r *models.ScanResult, githubConfs map[string]c.GitHubConf) error {
|
||||
func DetectGitHubCves(r *models.ScanResult, githubConfs map[string]c.GitHubConf, ignoreDismissed bool) error {
|
||||
if len(githubConfs) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -205,7 +228,7 @@ func DetectGitHubCves(r *models.ScanResult, githubConfs map[string]c.GitHubConf)
|
||||
return xerrors.Errorf("Failed to parse GitHub owner/repo: %s", ownerRepo)
|
||||
}
|
||||
owner, repo := ss[0], ss[1]
|
||||
n, err := github.DetectGitHubSecurityAlerts(r, owner, repo, setting.Token)
|
||||
n, err := DetectGitHubSecurityAlerts(r, owner, repo, setting.Token, ignoreDismissed)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to access GitHub Security Alerts: %w", err)
|
||||
}
|
||||
@@ -221,7 +244,7 @@ func DetectWordPressCves(r *models.ScanResult, wpCnf *c.WpScanConf) error {
|
||||
return nil
|
||||
}
|
||||
util.Log.Infof("Detect WordPress CVE. pkgs: %d ", len(r.WordPressPackages))
|
||||
n, err := wordpress.DetectWordPressCves(r, wpCnf)
|
||||
n, err := detectWordPressCves(r, wpCnf)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to detect WordPress CVE: %w", err)
|
||||
}
|
||||
@@ -328,35 +351,35 @@ func detectPkgsCvesWithOval(driver ovaldb.DB, r *models.ScanResult) error {
|
||||
var ovalFamily string
|
||||
|
||||
switch r.Family {
|
||||
case c.Debian, c.Raspbian:
|
||||
case constant.Debian, constant.Raspbian:
|
||||
ovalClient = oval.NewDebian()
|
||||
ovalFamily = c.Debian
|
||||
case c.Ubuntu:
|
||||
ovalFamily = constant.Debian
|
||||
case constant.Ubuntu:
|
||||
ovalClient = oval.NewUbuntu()
|
||||
ovalFamily = c.Ubuntu
|
||||
case c.RedHat:
|
||||
ovalFamily = constant.Ubuntu
|
||||
case constant.RedHat:
|
||||
ovalClient = oval.NewRedhat()
|
||||
ovalFamily = c.RedHat
|
||||
case c.CentOS:
|
||||
ovalFamily = constant.RedHat
|
||||
case constant.CentOS:
|
||||
ovalClient = oval.NewCentOS()
|
||||
//use RedHat's OVAL
|
||||
ovalFamily = c.RedHat
|
||||
case c.Oracle:
|
||||
ovalFamily = constant.RedHat
|
||||
case constant.Oracle:
|
||||
ovalClient = oval.NewOracle()
|
||||
ovalFamily = c.Oracle
|
||||
case c.SUSEEnterpriseServer:
|
||||
ovalFamily = constant.Oracle
|
||||
case constant.SUSEEnterpriseServer:
|
||||
// TODO other suse family
|
||||
ovalClient = oval.NewSUSE()
|
||||
ovalFamily = c.SUSEEnterpriseServer
|
||||
case c.Alpine:
|
||||
ovalFamily = constant.SUSEEnterpriseServer
|
||||
case constant.Alpine:
|
||||
ovalClient = oval.NewAlpine()
|
||||
ovalFamily = c.Alpine
|
||||
case c.Amazon:
|
||||
ovalFamily = constant.Alpine
|
||||
case constant.Amazon:
|
||||
ovalClient = oval.NewAmazon()
|
||||
ovalFamily = c.Amazon
|
||||
case c.FreeBSD, c.Windows:
|
||||
ovalFamily = constant.Amazon
|
||||
case constant.FreeBSD, constant.Windows:
|
||||
return nil
|
||||
case c.ServerTypePseudo:
|
||||
case constant.ServerTypePseudo:
|
||||
return nil
|
||||
default:
|
||||
if r.Family == "" {
|
||||
@@ -484,7 +507,7 @@ func fillCweDict(r *models.ScanResult) {
|
||||
entry.En = &cwe.Cwe{CweID: id}
|
||||
}
|
||||
|
||||
if c.Conf.Lang == "ja" {
|
||||
if r.Lang == "ja" {
|
||||
if e, ok := cwe.CweDictJa[id]; ok {
|
||||
if rank, ok := cwe.OwaspTopTen2017[id]; ok {
|
||||
entry.OwaspTopTen2017 = rank
|
||||
@@ -1,4 +1,4 @@
|
||||
package github
|
||||
package detector
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/errof"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"golang.org/x/oauth2"
|
||||
@@ -18,7 +17,7 @@ import (
|
||||
// DetectGitHubSecurityAlerts access to owner/repo on GitHub and fetch security alerts of the repository via GitHub API v4 GraphQL and then set to the given ScanResult.
|
||||
// https://help.github.com/articles/about-security-alerts-for-vulnerable-dependencies/
|
||||
//TODO move to report
|
||||
func DetectGitHubSecurityAlerts(r *models.ScanResult, owner, repo, token string) (nCVEs int, err error) {
|
||||
func DetectGitHubSecurityAlerts(r *models.ScanResult, owner, repo, token string, ignoreDismissed bool) (nCVEs int, err error) {
|
||||
src := oauth2.StaticTokenSource(
|
||||
&oauth2.Token{AccessToken: token},
|
||||
)
|
||||
@@ -74,7 +73,7 @@ func DetectGitHubSecurityAlerts(r *models.ScanResult, owner, repo, token string)
|
||||
}
|
||||
|
||||
for _, v := range alerts.Data.Repository.VulnerabilityAlerts.Edges {
|
||||
if config.Conf.IgnoreGitHubDismissed && v.Node.DismissReason != "" {
|
||||
if ignoreDismissed && v.Node.DismissReason != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package libmanager
|
||||
package detector
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -12,13 +12,12 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
"k8s.io/utils/clock"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
)
|
||||
|
||||
// DetectLibsCves fills LibraryScanner information
|
||||
func DetectLibsCves(r *models.ScanResult) (err error) {
|
||||
func DetectLibsCves(r *models.ScanResult, cacheDir string, noProgress bool) (err error) {
|
||||
totalCnt := 0
|
||||
if len(r.LibraryScanners) == 0 {
|
||||
return
|
||||
@@ -31,11 +30,11 @@ func DetectLibsCves(r *models.ScanResult) (err error) {
|
||||
}
|
||||
|
||||
util.Log.Info("Updating library db...")
|
||||
if err := downloadDB(config.Version, config.Conf.TrivyCacheDBDir, config.Conf.NoProgress, false, false); err != nil {
|
||||
if err := downloadDB("", cacheDir, noProgress, false, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := db2.Init(config.Conf.TrivyCacheDBDir); err != nil {
|
||||
if err := db2.Init(cacheDir); err != nil {
|
||||
return err
|
||||
}
|
||||
defer db2.Close()
|
||||
270
detector/util.go
Normal file
270
detector/util.go
Normal file
@@ -0,0 +1,270 @@
|
||||
package detector
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
func reuseScannedCves(r *models.ScanResult) bool {
|
||||
switch r.Family {
|
||||
case constant.FreeBSD, constant.Raspbian:
|
||||
return true
|
||||
}
|
||||
if isTrivyResult(r) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isTrivyResult(r *models.ScanResult) bool {
|
||||
_, ok := r.Optional["trivy-target"]
|
||||
return ok
|
||||
}
|
||||
|
||||
func needToRefreshCve(r models.ScanResult) bool {
|
||||
for _, cve := range r.ScannedCves {
|
||||
if 0 < len(cve.CveContents) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func loadPrevious(currs models.ScanResults) (prevs models.ScanResults, err error) {
|
||||
dirs, err := ListValidJSONDirs()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, result := range currs {
|
||||
filename := result.ServerName + ".json"
|
||||
if result.Container.Name != "" {
|
||||
filename = fmt.Sprintf("%s@%s.json", result.Container.Name, result.ServerName)
|
||||
}
|
||||
for _, dir := range dirs[1:] {
|
||||
path := filepath.Join(dir, filename)
|
||||
r, err := loadOneServerScanResult(path)
|
||||
if err != nil {
|
||||
util.Log.Debugf("%+v", err)
|
||||
continue
|
||||
}
|
||||
if r.Family == result.Family && r.Release == result.Release {
|
||||
prevs = append(prevs, *r)
|
||||
util.Log.Infof("Previous json found: %s", path)
|
||||
break
|
||||
} else {
|
||||
util.Log.Infof("Previous json is different family.Release: %s, pre: %s.%s cur: %s.%s",
|
||||
path, r.Family, r.Release, result.Family, result.Release)
|
||||
}
|
||||
}
|
||||
}
|
||||
return prevs, nil
|
||||
}
|
||||
|
||||
func diff(curResults, preResults models.ScanResults, isPlus, isMinus bool) (diffed models.ScanResults) {
|
||||
for _, current := range curResults {
|
||||
found := false
|
||||
var previous models.ScanResult
|
||||
for _, r := range preResults {
|
||||
if current.ServerName == r.ServerName && current.Container.Name == r.Container.Name {
|
||||
found = true
|
||||
previous = r
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
diffed = append(diffed, current)
|
||||
continue
|
||||
}
|
||||
|
||||
cves := models.VulnInfos{}
|
||||
if isPlus {
|
||||
cves = getPlusDiffCves(previous, current)
|
||||
}
|
||||
if isMinus {
|
||||
minus := getMinusDiffCves(previous, current)
|
||||
if len(cves) == 0 {
|
||||
cves = minus
|
||||
} else {
|
||||
for k, v := range minus {
|
||||
cves[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
packages := models.Packages{}
|
||||
for _, s := range cves {
|
||||
for _, affected := range s.AffectedPackages {
|
||||
var p models.Package
|
||||
if s.DiffStatus == models.DiffPlus {
|
||||
p = current.Packages[affected.Name]
|
||||
} else {
|
||||
p = previous.Packages[affected.Name]
|
||||
}
|
||||
packages[affected.Name] = p
|
||||
}
|
||||
}
|
||||
current.ScannedCves = cves
|
||||
current.Packages = packages
|
||||
diffed = append(diffed, current)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getPlusDiffCves(previous, current models.ScanResult) models.VulnInfos {
|
||||
previousCveIDsSet := map[string]bool{}
|
||||
for _, previousVulnInfo := range previous.ScannedCves {
|
||||
previousCveIDsSet[previousVulnInfo.CveID] = true
|
||||
}
|
||||
|
||||
new := models.VulnInfos{}
|
||||
updated := models.VulnInfos{}
|
||||
for _, v := range current.ScannedCves {
|
||||
if previousCveIDsSet[v.CveID] {
|
||||
if isCveInfoUpdated(v.CveID, previous, current) {
|
||||
v.DiffStatus = models.DiffPlus
|
||||
updated[v.CveID] = v
|
||||
util.Log.Debugf("updated: %s", v.CveID)
|
||||
|
||||
// TODO commented out because a bug of diff logic when multiple oval defs found for a certain CVE-ID and same updated_at
|
||||
// if these OVAL defs have different affected packages, this logic detects as updated.
|
||||
// This logic will be uncomented after integration with gost https://github.com/knqyf263/gost
|
||||
// } else if isCveFixed(v, previous) {
|
||||
// updated[v.CveID] = v
|
||||
// util.Log.Debugf("fixed: %s", v.CveID)
|
||||
|
||||
} else {
|
||||
util.Log.Debugf("same: %s", v.CveID)
|
||||
}
|
||||
} else {
|
||||
util.Log.Debugf("new: %s", v.CveID)
|
||||
v.DiffStatus = models.DiffPlus
|
||||
new[v.CveID] = v
|
||||
}
|
||||
}
|
||||
|
||||
if len(updated) == 0 && len(new) == 0 {
|
||||
util.Log.Infof("%s: There are %d vulnerabilities, but no difference between current result and previous one.", current.FormatServerName(), len(current.ScannedCves))
|
||||
}
|
||||
|
||||
for cveID, vuln := range new {
|
||||
updated[cveID] = vuln
|
||||
}
|
||||
return updated
|
||||
}
|
||||
|
||||
func getMinusDiffCves(previous, current models.ScanResult) models.VulnInfos {
|
||||
currentCveIDsSet := map[string]bool{}
|
||||
for _, currentVulnInfo := range current.ScannedCves {
|
||||
currentCveIDsSet[currentVulnInfo.CveID] = true
|
||||
}
|
||||
|
||||
clear := models.VulnInfos{}
|
||||
for _, v := range previous.ScannedCves {
|
||||
if !currentCveIDsSet[v.CveID] {
|
||||
v.DiffStatus = models.DiffMinus
|
||||
clear[v.CveID] = v
|
||||
util.Log.Debugf("clear: %s", v.CveID)
|
||||
}
|
||||
}
|
||||
if len(clear) == 0 {
|
||||
util.Log.Infof("%s: There are %d vulnerabilities, but no difference between current result and previous one.", current.FormatServerName(), len(current.ScannedCves))
|
||||
}
|
||||
|
||||
return clear
|
||||
}
|
||||
|
||||
func isCveInfoUpdated(cveID string, previous, current models.ScanResult) bool {
|
||||
cTypes := []models.CveContentType{
|
||||
models.Nvd,
|
||||
models.Jvn,
|
||||
models.NewCveContentType(current.Family),
|
||||
}
|
||||
|
||||
prevLastModified := map[models.CveContentType]time.Time{}
|
||||
preVinfo, ok := previous.ScannedCves[cveID]
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
for _, cType := range cTypes {
|
||||
if content, ok := preVinfo.CveContents[cType]; ok {
|
||||
prevLastModified[cType] = content.LastModified
|
||||
}
|
||||
}
|
||||
|
||||
curLastModified := map[models.CveContentType]time.Time{}
|
||||
curVinfo, ok := current.ScannedCves[cveID]
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
for _, cType := range cTypes {
|
||||
if content, ok := curVinfo.CveContents[cType]; ok {
|
||||
curLastModified[cType] = content.LastModified
|
||||
}
|
||||
}
|
||||
|
||||
for _, t := range cTypes {
|
||||
if !curLastModified[t].Equal(prevLastModified[t]) {
|
||||
util.Log.Debugf("%s LastModified not equal: \n%s\n%s",
|
||||
cveID, curLastModified[t], prevLastModified[t])
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// jsonDirPattern is file name pattern of JSON directory
|
||||
// 2016-11-16T10:43:28+09:00
|
||||
// 2016-11-16T10:43:28Z
|
||||
var jsonDirPattern = regexp.MustCompile(
|
||||
`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:Z|[+-]\d{2}:\d{2})$`)
|
||||
|
||||
// ListValidJSONDirs returns valid json directory as array
|
||||
// Returned array is sorted so that recent directories are at the head
|
||||
func ListValidJSONDirs() (dirs []string, err error) {
|
||||
var dirInfo []os.FileInfo
|
||||
if dirInfo, err = ioutil.ReadDir(config.Conf.ResultsDir); err != nil {
|
||||
err = xerrors.Errorf("Failed to read %s: %w",
|
||||
config.Conf.ResultsDir, err)
|
||||
return
|
||||
}
|
||||
for _, d := range dirInfo {
|
||||
if d.IsDir() && jsonDirPattern.MatchString(d.Name()) {
|
||||
jsonDir := filepath.Join(config.Conf.ResultsDir, d.Name())
|
||||
dirs = append(dirs, jsonDir)
|
||||
}
|
||||
}
|
||||
sort.Slice(dirs, func(i, j int) bool {
|
||||
return dirs[j] < dirs[i]
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// loadOneServerScanResult read JSON data of one server
|
||||
func loadOneServerScanResult(jsonFile string) (*models.ScanResult, error) {
|
||||
var (
|
||||
data []byte
|
||||
err error
|
||||
)
|
||||
if data, err = ioutil.ReadFile(jsonFile); err != nil {
|
||||
return nil, xerrors.Errorf("Failed to read %s: %w", jsonFile, err)
|
||||
}
|
||||
result := &models.ScanResult{}
|
||||
if err := json.Unmarshal(data, result); err != nil {
|
||||
return nil, xerrors.Errorf("Failed to parse %s: %w", jsonFile, err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package wordpress
|
||||
package detector
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -50,8 +50,7 @@ type References struct {
|
||||
|
||||
// DetectWordPressCves access to wpscan and fetch scurity alerts and then set to the given ScanResult.
|
||||
// https://wpscan.com/
|
||||
// TODO move to report
|
||||
func DetectWordPressCves(r *models.ScanResult, cnf *c.WpScanConf) (int, error) {
|
||||
func detectWordPressCves(r *models.ScanResult, cnf *c.WpScanConf) (int, error) {
|
||||
if len(r.WordPressPackages) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package wordpress
|
||||
package detector
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
1
go.mod
1
go.mod
@@ -43,7 +43,6 @@ require (
|
||||
github.com/spf13/cobra v1.1.1
|
||||
github.com/takuzoo3868/go-msfdb v0.1.3
|
||||
github.com/vulsio/go-exploitdb v0.1.4
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
||||
golang.org/x/oauth2 v0.0.0-20210125201302-af13f521f196
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
|
||||
k8s.io/utils v0.0.0-20210111153108-fddb29f9d009
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
"github.com/knqyf263/gost/db"
|
||||
@@ -56,7 +57,7 @@ func (deb Debian) DetectUnfixed(driver db.DB, r *models.ScanResult, _ bool) (nCV
|
||||
|
||||
// Debian Security Tracker does not support Package for Raspbian, so skip it.
|
||||
var scanResult models.ScanResult
|
||||
if r.Family != config.Raspbian {
|
||||
if r.Family != constant.Raspbian {
|
||||
scanResult = *r
|
||||
} else {
|
||||
scanResult = r.RemoveRaspbianPackFromResult()
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
package gost
|
||||
|
||||
import (
|
||||
cnf "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/knqyf263/gost/db"
|
||||
|
||||
"github.com/future-architect/vuls/constant"
|
||||
)
|
||||
|
||||
// Client is the interface of OVAL client.
|
||||
@@ -17,11 +18,11 @@ type Client interface {
|
||||
// NewClient make Client by family
|
||||
func NewClient(family string) Client {
|
||||
switch family {
|
||||
case cnf.RedHat, cnf.CentOS:
|
||||
case constant.RedHat, constant.CentOS:
|
||||
return RedHat{}
|
||||
case cnf.Debian, cnf.Raspbian:
|
||||
case constant.Debian, constant.Raspbian:
|
||||
return Debian{}
|
||||
case cnf.Windows:
|
||||
case constant.Windows:
|
||||
return Microsoft{}
|
||||
default:
|
||||
return Pseudo{}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/cwe"
|
||||
"github.com/future-architect/vuls/util"
|
||||
)
|
||||
@@ -18,31 +19,31 @@ type ScanResults []ScanResult
|
||||
|
||||
// ScanResult has the result of scanned CVE information.
|
||||
type ScanResult struct {
|
||||
JSONVersion int `json:"jsonVersion"`
|
||||
Lang string `json:"lang"`
|
||||
ServerUUID string `json:"serverUUID"`
|
||||
ServerName string `json:"serverName"` // TOML Section key
|
||||
Family string `json:"family"`
|
||||
Release string `json:"release"`
|
||||
Container Container `json:"container"`
|
||||
Platform Platform `json:"platform"`
|
||||
IPv4Addrs []string `json:"ipv4Addrs,omitempty"` // only global unicast address (https://golang.org/pkg/net/#IP.IsGlobalUnicast)
|
||||
IPv6Addrs []string `json:"ipv6Addrs,omitempty"` // only global unicast address (https://golang.org/pkg/net/#IP.IsGlobalUnicast)
|
||||
IPSIdentifiers map[config.IPS]string `json:"ipsIdentifiers,omitempty"`
|
||||
ScannedAt time.Time `json:"scannedAt"`
|
||||
ScanMode string `json:"scanMode"`
|
||||
ScannedVersion string `json:"scannedVersion"`
|
||||
ScannedRevision string `json:"scannedRevision"`
|
||||
ScannedBy string `json:"scannedBy"`
|
||||
ScannedVia string `json:"scannedVia"`
|
||||
ScannedIPv4Addrs []string `json:"scannedIpv4Addrs,omitempty"`
|
||||
ScannedIPv6Addrs []string `json:"scannedIpv6Addrs,omitempty"`
|
||||
ReportedAt time.Time `json:"reportedAt"`
|
||||
ReportedVersion string `json:"reportedVersion"`
|
||||
ReportedRevision string `json:"reportedRevision"`
|
||||
ReportedBy string `json:"reportedBy"`
|
||||
Errors []string `json:"errors"`
|
||||
Warnings []string `json:"warnings"`
|
||||
JSONVersion int `json:"jsonVersion"`
|
||||
Lang string `json:"lang"`
|
||||
ServerUUID string `json:"serverUUID"`
|
||||
ServerName string `json:"serverName"` // TOML Section key
|
||||
Family string `json:"family"`
|
||||
Release string `json:"release"`
|
||||
Container Container `json:"container"`
|
||||
Platform Platform `json:"platform"`
|
||||
IPv4Addrs []string `json:"ipv4Addrs,omitempty"` // only global unicast address (https://golang.org/pkg/net/#IP.IsGlobalUnicast)
|
||||
IPv6Addrs []string `json:"ipv6Addrs,omitempty"` // only global unicast address (https://golang.org/pkg/net/#IP.IsGlobalUnicast)
|
||||
IPSIdentifiers map[string]string `json:"ipsIdentifiers,omitempty"`
|
||||
ScannedAt time.Time `json:"scannedAt"`
|
||||
ScanMode string `json:"scanMode"`
|
||||
ScannedVersion string `json:"scannedVersion"`
|
||||
ScannedRevision string `json:"scannedRevision"`
|
||||
ScannedBy string `json:"scannedBy"`
|
||||
ScannedVia string `json:"scannedVia"`
|
||||
ScannedIPv4Addrs []string `json:"scannedIpv4Addrs,omitempty"`
|
||||
ScannedIPv6Addrs []string `json:"scannedIpv6Addrs,omitempty"`
|
||||
ReportedAt time.Time `json:"reportedAt"`
|
||||
ReportedVersion string `json:"reportedVersion"`
|
||||
ReportedRevision string `json:"reportedRevision"`
|
||||
ReportedBy string `json:"reportedBy"`
|
||||
Errors []string `json:"errors"`
|
||||
Warnings []string `json:"warnings"`
|
||||
|
||||
ScannedCves VulnInfos `json:"scannedCves"`
|
||||
RunningKernel Kernel `json:"runningKernel"`
|
||||
@@ -56,66 +57,22 @@ type ScanResult struct {
|
||||
Config struct {
|
||||
Scan config.Config `json:"scan"`
|
||||
Report config.Config `json:"report"`
|
||||
} `json:"config"`
|
||||
} `json:"constant"`
|
||||
}
|
||||
|
||||
// CweDict is a dictionary for CWE
|
||||
type CweDict map[string]CweDictEntry
|
||||
|
||||
// Get the name, url, top10URL for the specified cweID, lang
|
||||
func (c CweDict) Get(cweID, lang string) (name, url, top10Rank, top10URL, cweTop25Rank, cweTop25URL, sansTop25Rank, sansTop25URL string) {
|
||||
cweNum := strings.TrimPrefix(cweID, "CWE-")
|
||||
switch config.Conf.Lang {
|
||||
case "ja":
|
||||
if dict, ok := c[cweNum]; ok && dict.OwaspTopTen2017 != "" {
|
||||
top10Rank = dict.OwaspTopTen2017
|
||||
top10URL = cwe.OwaspTopTen2017GitHubURLJa[dict.OwaspTopTen2017]
|
||||
}
|
||||
if dict, ok := c[cweNum]; ok && dict.CweTopTwentyfive2019 != "" {
|
||||
cweTop25Rank = dict.CweTopTwentyfive2019
|
||||
cweTop25URL = cwe.CweTopTwentyfive2019URL
|
||||
}
|
||||
if dict, ok := c[cweNum]; ok && dict.SansTopTwentyfive != "" {
|
||||
sansTop25Rank = dict.SansTopTwentyfive
|
||||
sansTop25URL = cwe.SansTopTwentyfiveURL
|
||||
}
|
||||
if dict, ok := cwe.CweDictJa[cweNum]; ok {
|
||||
name = dict.Name
|
||||
url = fmt.Sprintf("http://jvndb.jvn.jp/ja/cwe/%s.html", cweID)
|
||||
} else {
|
||||
if dict, ok := cwe.CweDictEn[cweNum]; ok {
|
||||
name = dict.Name
|
||||
}
|
||||
url = fmt.Sprintf("https://cwe.mitre.org/data/definitions/%s.html", cweID)
|
||||
}
|
||||
default:
|
||||
if dict, ok := c[cweNum]; ok && dict.OwaspTopTen2017 != "" {
|
||||
top10Rank = dict.OwaspTopTen2017
|
||||
top10URL = cwe.OwaspTopTen2017GitHubURLEn[dict.OwaspTopTen2017]
|
||||
}
|
||||
if dict, ok := c[cweNum]; ok && dict.CweTopTwentyfive2019 != "" {
|
||||
cweTop25Rank = dict.CweTopTwentyfive2019
|
||||
cweTop25URL = cwe.CweTopTwentyfive2019URL
|
||||
}
|
||||
if dict, ok := c[cweNum]; ok && dict.SansTopTwentyfive != "" {
|
||||
sansTop25Rank = dict.SansTopTwentyfive
|
||||
sansTop25URL = cwe.SansTopTwentyfiveURL
|
||||
}
|
||||
url = fmt.Sprintf("https://cwe.mitre.org/data/definitions/%s.html", cweID)
|
||||
if dict, ok := cwe.CweDictEn[cweNum]; ok {
|
||||
name = dict.Name
|
||||
}
|
||||
}
|
||||
return
|
||||
// Container has Container information
|
||||
type Container struct {
|
||||
ContainerID string `json:"containerID"`
|
||||
Name string `json:"name"`
|
||||
Image string `json:"image"`
|
||||
Type string `json:"type"`
|
||||
UUID string `json:"uuid"`
|
||||
}
|
||||
|
||||
// CweDictEntry is a entry of CWE
|
||||
type CweDictEntry struct {
|
||||
En *cwe.Cwe `json:"en,omitempty"`
|
||||
Ja *cwe.Cwe `json:"ja,omitempty"`
|
||||
OwaspTopTen2017 string `json:"owaspTopTen2017"`
|
||||
CweTopTwentyfive2019 string `json:"cweTopTwentyfive2019"`
|
||||
SansTopTwentyfive string `json:"sansTopTwentyfive"`
|
||||
// Platform has platform information
|
||||
type Platform struct {
|
||||
Name string `json:"name"` // aws or azure or gcp or other...
|
||||
InstanceID string `json:"instanceID"`
|
||||
}
|
||||
|
||||
// Kernel has the Release, version and whether need restart
|
||||
@@ -138,26 +95,7 @@ func (r ScanResult) FilterByCvssOver(over float64) ScanResult {
|
||||
}
|
||||
|
||||
// FilterIgnoreCves is filter function.
|
||||
func (r ScanResult) FilterIgnoreCves() ScanResult {
|
||||
ignoreCves := []string{}
|
||||
if len(r.Container.Name) == 0 {
|
||||
//TODO pass by args
|
||||
ignoreCves = config.Conf.Servers[r.ServerName].IgnoreCves
|
||||
} else {
|
||||
//TODO pass by args
|
||||
if s, ok := config.Conf.Servers[r.ServerName]; ok {
|
||||
if con, ok := s.Containers[r.Container.Name]; ok {
|
||||
ignoreCves = con.IgnoreCves
|
||||
} else {
|
||||
return r
|
||||
}
|
||||
} else {
|
||||
util.Log.Errorf("%s is not found in config.toml",
|
||||
r.ServerName)
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
func (r ScanResult) FilterIgnoreCves(ignoreCves []string) ScanResult {
|
||||
filtered := r.ScannedCves.Find(func(v VulnInfo) bool {
|
||||
for _, c := range ignoreCves {
|
||||
if v.CveID == c {
|
||||
@@ -191,25 +129,7 @@ func (r ScanResult) FilterUnfixed(ignoreUnfixed bool) ScanResult {
|
||||
}
|
||||
|
||||
// FilterIgnorePkgs is filter function.
|
||||
func (r ScanResult) FilterIgnorePkgs() ScanResult {
|
||||
var ignorePkgsRegexps []string
|
||||
if len(r.Container.Name) == 0 {
|
||||
//TODO pass by args
|
||||
ignorePkgsRegexps = config.Conf.Servers[r.ServerName].IgnorePkgsRegexp
|
||||
} else {
|
||||
if s, ok := config.Conf.Servers[r.ServerName]; ok {
|
||||
if con, ok := s.Containers[r.Container.Name]; ok {
|
||||
ignorePkgsRegexps = con.IgnorePkgsRegexp
|
||||
} else {
|
||||
return r
|
||||
}
|
||||
} else {
|
||||
util.Log.Errorf("%s is not found in config.toml",
|
||||
r.ServerName)
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
func (r ScanResult) FilterIgnorePkgs(ignorePkgsRegexps []string) ScanResult {
|
||||
regexps := []*regexp.Regexp{}
|
||||
for _, pkgRegexp := range ignorePkgsRegexps {
|
||||
re, err := regexp.Compile(pkgRegexp)
|
||||
@@ -344,7 +264,7 @@ func (r ScanResult) FormatTextReportHeader() string {
|
||||
buf.WriteString("=")
|
||||
}
|
||||
|
||||
pkgs := r.FormatUpdatablePacksSummary()
|
||||
pkgs := r.FormatUpdatablePkgsSummary()
|
||||
if 0 < len(r.WordPressPackages) {
|
||||
pkgs = fmt.Sprintf("%s, %d WordPress pkgs", pkgs, len(r.WordPressPackages))
|
||||
}
|
||||
@@ -363,9 +283,10 @@ func (r ScanResult) FormatTextReportHeader() string {
|
||||
pkgs)
|
||||
}
|
||||
|
||||
// FormatUpdatablePacksSummary returns a summary of updatable packages
|
||||
func (r ScanResult) FormatUpdatablePacksSummary() string {
|
||||
if !r.isDisplayUpdatableNum() {
|
||||
// FormatUpdatablePkgsSummary returns a summary of updatable packages
|
||||
func (r ScanResult) FormatUpdatablePkgsSummary() string {
|
||||
mode := r.Config.Scan.Servers[r.ServerName].Mode
|
||||
if !r.isDisplayUpdatableNum(mode) {
|
||||
return fmt.Sprintf("%d installed", len(r.Packages))
|
||||
}
|
||||
|
||||
@@ -420,16 +341,11 @@ func (r ScanResult) FormatAlertSummary() string {
|
||||
return fmt.Sprintf("en: %d, ja: %d alerts", enCnt, jaCnt)
|
||||
}
|
||||
|
||||
func (r ScanResult) isDisplayUpdatableNum() bool {
|
||||
if r.Family == config.FreeBSD {
|
||||
func (r ScanResult) isDisplayUpdatableNum(mode config.ScanMode) bool {
|
||||
if r.Family == constant.FreeBSD {
|
||||
return false
|
||||
}
|
||||
|
||||
var mode config.ScanMode
|
||||
//TODO pass by args
|
||||
s, _ := config.Conf.Servers[r.ServerName]
|
||||
mode = s.Mode
|
||||
|
||||
if mode.IsOffline() {
|
||||
return false
|
||||
}
|
||||
@@ -438,11 +354,11 @@ func (r ScanResult) isDisplayUpdatableNum() bool {
|
||||
}
|
||||
if mode.IsFast() {
|
||||
switch r.Family {
|
||||
case config.RedHat,
|
||||
config.Oracle,
|
||||
config.Debian,
|
||||
config.Ubuntu,
|
||||
config.Raspbian:
|
||||
case constant.RedHat,
|
||||
constant.Oracle,
|
||||
constant.Debian,
|
||||
constant.Ubuntu,
|
||||
constant.Raspbian:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
@@ -456,34 +372,9 @@ func (r ScanResult) IsContainer() bool {
|
||||
return 0 < len(r.Container.ContainerID)
|
||||
}
|
||||
|
||||
// IsDeepScanMode checks if the scan mode is deep scan mode.
|
||||
func (r ScanResult) IsDeepScanMode() bool {
|
||||
for _, s := range r.Config.Scan.Servers {
|
||||
if ok := s.Mode.IsDeep(); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Container has Container information
|
||||
type Container struct {
|
||||
ContainerID string `json:"containerID"`
|
||||
Name string `json:"name"`
|
||||
Image string `json:"image"`
|
||||
Type string `json:"type"`
|
||||
UUID string `json:"uuid"`
|
||||
}
|
||||
|
||||
// Platform has platform information
|
||||
type Platform struct {
|
||||
Name string `json:"name"` // aws or azure or gcp or other...
|
||||
InstanceID string `json:"instanceID"`
|
||||
}
|
||||
|
||||
// RemoveRaspbianPackFromResult is for Raspberry Pi and removes the Raspberry Pi dedicated package from ScanResult.
|
||||
func (r ScanResult) RemoveRaspbianPackFromResult() ScanResult {
|
||||
if r.Family != config.Raspbian {
|
||||
if r.Family != constant.Raspbian {
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -528,3 +419,99 @@ func (r ScanResult) ClearFields(targetTagNames []string) ScanResult {
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// CheckEOL checks the EndOfLife of the OS
|
||||
func (r *ScanResult) CheckEOL() {
|
||||
switch r.Family {
|
||||
case constant.ServerTypePseudo, constant.Raspbian:
|
||||
return
|
||||
}
|
||||
|
||||
eol, found := config.GetEOL(r.Family, r.Release)
|
||||
if !found {
|
||||
r.Warnings = append(r.Warnings,
|
||||
fmt.Sprintf("Failed to check EOL. Register the issue to https://github.com/future-architect/vuls/issues with the information in `Family: %s Release: %s`",
|
||||
r.Family, r.Release))
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
if eol.IsStandardSupportEnded(now) {
|
||||
r.Warnings = append(r.Warnings, "Standard OS support is EOL(End-of-Life). Purchase extended support if available or Upgrading your OS is strongly recommended.")
|
||||
if eol.ExtendedSupportUntil.IsZero() {
|
||||
return
|
||||
}
|
||||
if !eol.IsExtendedSuppportEnded(now) {
|
||||
r.Warnings = append(r.Warnings,
|
||||
fmt.Sprintf("Extended support available until %s. Check the vendor site.",
|
||||
eol.ExtendedSupportUntil.Format("2006-01-02")))
|
||||
} else {
|
||||
r.Warnings = append(r.Warnings,
|
||||
"Extended support is also EOL. There are many Vulnerabilities that are not detected, Upgrading your OS strongly recommended.")
|
||||
}
|
||||
} else if !eol.StandardSupportUntil.IsZero() &&
|
||||
now.AddDate(0, 3, 0).After(eol.StandardSupportUntil) {
|
||||
r.Warnings = append(r.Warnings,
|
||||
fmt.Sprintf("Standard OS support will be end in 3 months. EOL date: %s",
|
||||
eol.StandardSupportUntil.Format("2006-01-02")))
|
||||
}
|
||||
}
|
||||
|
||||
// CweDict is a dictionary for CWE
|
||||
type CweDict map[string]CweDictEntry
|
||||
|
||||
// Get the name, url, top10URL for the specified cweID, lang
|
||||
func (c CweDict) Get(cweID, lang string) (name, url, top10Rank, top10URL, cweTop25Rank, cweTop25URL, sansTop25Rank, sansTop25URL string) {
|
||||
cweNum := strings.TrimPrefix(cweID, "CWE-")
|
||||
switch lang {
|
||||
case "ja":
|
||||
if dict, ok := c[cweNum]; ok && dict.OwaspTopTen2017 != "" {
|
||||
top10Rank = dict.OwaspTopTen2017
|
||||
top10URL = cwe.OwaspTopTen2017GitHubURLJa[dict.OwaspTopTen2017]
|
||||
}
|
||||
if dict, ok := c[cweNum]; ok && dict.CweTopTwentyfive2019 != "" {
|
||||
cweTop25Rank = dict.CweTopTwentyfive2019
|
||||
cweTop25URL = cwe.CweTopTwentyfive2019URL
|
||||
}
|
||||
if dict, ok := c[cweNum]; ok && dict.SansTopTwentyfive != "" {
|
||||
sansTop25Rank = dict.SansTopTwentyfive
|
||||
sansTop25URL = cwe.SansTopTwentyfiveURL
|
||||
}
|
||||
if dict, ok := cwe.CweDictJa[cweNum]; ok {
|
||||
name = dict.Name
|
||||
url = fmt.Sprintf("http://jvndb.jvn.jp/ja/cwe/%s.html", cweID)
|
||||
} else {
|
||||
if dict, ok := cwe.CweDictEn[cweNum]; ok {
|
||||
name = dict.Name
|
||||
}
|
||||
url = fmt.Sprintf("https://cwe.mitre.org/data/definitions/%s.html", cweID)
|
||||
}
|
||||
default:
|
||||
if dict, ok := c[cweNum]; ok && dict.OwaspTopTen2017 != "" {
|
||||
top10Rank = dict.OwaspTopTen2017
|
||||
top10URL = cwe.OwaspTopTen2017GitHubURLEn[dict.OwaspTopTen2017]
|
||||
}
|
||||
if dict, ok := c[cweNum]; ok && dict.CweTopTwentyfive2019 != "" {
|
||||
cweTop25Rank = dict.CweTopTwentyfive2019
|
||||
cweTop25URL = cwe.CweTopTwentyfive2019URL
|
||||
}
|
||||
if dict, ok := c[cweNum]; ok && dict.SansTopTwentyfive != "" {
|
||||
sansTop25Rank = dict.SansTopTwentyfive
|
||||
sansTop25URL = cwe.SansTopTwentyfiveURL
|
||||
}
|
||||
url = fmt.Sprintf("https://cwe.mitre.org/data/definitions/%s.html", cweID)
|
||||
if dict, ok := cwe.CweDictEn[cweNum]; ok {
|
||||
name = dict.Name
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CweDictEntry is a entry of CWE
|
||||
type CweDictEntry struct {
|
||||
En *cwe.Cwe `json:"en,omitempty"`
|
||||
Ja *cwe.Cwe `json:"ja,omitempty"`
|
||||
OwaspTopTen2017 string `json:"owaspTopTen2017"`
|
||||
CweTopTwentyfive2019 string `json:"cweTopTwentyfive2019"`
|
||||
SansTopTwentyfive string `json:"sansTopTwentyfive"`
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/k0kubun/pp"
|
||||
)
|
||||
|
||||
@@ -233,80 +234,10 @@ func TestFilterIgnoreCveIDs(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
config.Conf.Servers = map[string]config.ServerInfo{
|
||||
"name": {IgnoreCves: tt.in.cves},
|
||||
}
|
||||
actual := tt.in.rs.FilterIgnoreCves()
|
||||
for k := range tt.out.ScannedCves {
|
||||
if !reflect.DeepEqual(tt.out.ScannedCves[k], actual.ScannedCves[k]) {
|
||||
o := pp.Sprintf("%v", tt.out.ScannedCves[k])
|
||||
a := pp.Sprintf("%v", actual.ScannedCves[k])
|
||||
t.Errorf("[%s] expected: %v\n actual: %v\n", k, o, a)
|
||||
}
|
||||
}
|
||||
for k := range actual.ScannedCves {
|
||||
if !reflect.DeepEqual(tt.out.ScannedCves[k], actual.ScannedCves[k]) {
|
||||
o := pp.Sprintf("%v", tt.out.ScannedCves[k])
|
||||
a := pp.Sprintf("%v", actual.ScannedCves[k])
|
||||
t.Errorf("[%s] expected: %v\n actual: %v\n", k, o, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterIgnoreCveIDsContainer(t *testing.T) {
|
||||
type in struct {
|
||||
cves []string
|
||||
rs ScanResult
|
||||
}
|
||||
var tests = []struct {
|
||||
in in
|
||||
out ScanResult
|
||||
}{
|
||||
{
|
||||
in: in{
|
||||
cves: []string{"CVE-2017-0002"},
|
||||
rs: ScanResult{
|
||||
ServerName: "name",
|
||||
Container: Container{Name: "dockerA"},
|
||||
ScannedCves: VulnInfos{
|
||||
"CVE-2017-0001": {
|
||||
CveID: "CVE-2017-0001",
|
||||
},
|
||||
"CVE-2017-0002": {
|
||||
CveID: "CVE-2017-0002",
|
||||
},
|
||||
"CVE-2017-0003": {
|
||||
CveID: "CVE-2017-0003",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
out: ScanResult{
|
||||
ServerName: "name",
|
||||
Container: Container{Name: "dockerA"},
|
||||
ScannedCves: VulnInfos{
|
||||
"CVE-2017-0001": {
|
||||
CveID: "CVE-2017-0001",
|
||||
},
|
||||
"CVE-2017-0003": {
|
||||
CveID: "CVE-2017-0003",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
config.Conf.Servers = map[string]config.ServerInfo{
|
||||
"name": {
|
||||
Containers: map[string]config.ContainerSetting{
|
||||
"dockerA": {
|
||||
IgnoreCves: tt.in.cves,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
actual := tt.in.rs.FilterIgnoreCves()
|
||||
// config.Conf.Servers = map[string]config.ServerInfo{
|
||||
// "name": {IgnoreCves: tt.in.cves},
|
||||
// }
|
||||
actual := tt.in.rs.FilterIgnoreCves(tt.in.cves)
|
||||
for k := range tt.out.ScannedCves {
|
||||
if !reflect.DeepEqual(tt.out.ScannedCves[k], actual.ScannedCves[k]) {
|
||||
o := pp.Sprintf("%v", tt.out.ScannedCves[k])
|
||||
@@ -491,131 +422,7 @@ func TestFilterIgnorePkgs(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
config.Conf.Servers = map[string]config.ServerInfo{
|
||||
"name": {IgnorePkgsRegexp: tt.in.ignorePkgsRegexp},
|
||||
}
|
||||
actual := tt.in.rs.FilterIgnorePkgs()
|
||||
for k := range tt.out.ScannedCves {
|
||||
if !reflect.DeepEqual(tt.out.ScannedCves[k], actual.ScannedCves[k]) {
|
||||
o := pp.Sprintf("%v", tt.out.ScannedCves[k])
|
||||
a := pp.Sprintf("%v", actual.ScannedCves[k])
|
||||
t.Errorf("[%s] expected: %v\n actual: %v\n", k, o, a)
|
||||
}
|
||||
}
|
||||
for k := range actual.ScannedCves {
|
||||
if !reflect.DeepEqual(tt.out.ScannedCves[k], actual.ScannedCves[k]) {
|
||||
o := pp.Sprintf("%v", tt.out.ScannedCves[k])
|
||||
a := pp.Sprintf("%v", actual.ScannedCves[k])
|
||||
t.Errorf("[%s] expected: %v\n actual: %v\n", k, o, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterIgnorePkgsContainer(t *testing.T) {
|
||||
type in struct {
|
||||
ignorePkgsRegexp []string
|
||||
rs ScanResult
|
||||
}
|
||||
var tests = []struct {
|
||||
in in
|
||||
out ScanResult
|
||||
}{
|
||||
{
|
||||
in: in{
|
||||
ignorePkgsRegexp: []string{"^kernel"},
|
||||
rs: ScanResult{
|
||||
ServerName: "name",
|
||||
Container: Container{Name: "dockerA"},
|
||||
ScannedCves: VulnInfos{
|
||||
"CVE-2017-0001": {
|
||||
CveID: "CVE-2017-0001",
|
||||
AffectedPackages: PackageFixStatuses{
|
||||
{Name: "kernel"},
|
||||
},
|
||||
},
|
||||
"CVE-2017-0002": {
|
||||
CveID: "CVE-2017-0002",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
out: ScanResult{
|
||||
ServerName: "name",
|
||||
Container: Container{Name: "dockerA"},
|
||||
ScannedCves: VulnInfos{
|
||||
"CVE-2017-0002": {
|
||||
CveID: "CVE-2017-0002",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
in: in{
|
||||
ignorePkgsRegexp: []string{"^kernel"},
|
||||
rs: ScanResult{
|
||||
ServerName: "name",
|
||||
Container: Container{Name: "dockerA"},
|
||||
ScannedCves: VulnInfos{
|
||||
"CVE-2017-0001": {
|
||||
CveID: "CVE-2017-0001",
|
||||
AffectedPackages: PackageFixStatuses{
|
||||
{Name: "kernel"},
|
||||
{Name: "vim"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
out: ScanResult{
|
||||
ServerName: "name",
|
||||
Container: Container{Name: "dockerA"},
|
||||
ScannedCves: VulnInfos{
|
||||
"CVE-2017-0001": {
|
||||
CveID: "CVE-2017-0001",
|
||||
AffectedPackages: PackageFixStatuses{
|
||||
{Name: "kernel"},
|
||||
{Name: "vim"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
in: in{
|
||||
ignorePkgsRegexp: []string{"^kernel", "^vim", "^bind"},
|
||||
rs: ScanResult{
|
||||
ServerName: "name",
|
||||
Container: Container{Name: "dockerA"},
|
||||
ScannedCves: VulnInfos{
|
||||
"CVE-2017-0001": {
|
||||
CveID: "CVE-2017-0001",
|
||||
AffectedPackages: PackageFixStatuses{
|
||||
{Name: "kernel"},
|
||||
{Name: "vim"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
out: ScanResult{
|
||||
ServerName: "name",
|
||||
Container: Container{Name: "dockerA"},
|
||||
ScannedCves: VulnInfos{},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
config.Conf.Servers = map[string]config.ServerInfo{
|
||||
"name": {
|
||||
Containers: map[string]config.ContainerSetting{
|
||||
"dockerA": {
|
||||
IgnorePkgsRegexp: tt.in.ignorePkgsRegexp,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
actual := tt.in.rs.FilterIgnorePkgs()
|
||||
actual := tt.in.rs.FilterIgnorePkgs(tt.in.ignorePkgsRegexp)
|
||||
for k := range tt.out.ScannedCves {
|
||||
if !reflect.DeepEqual(tt.out.ScannedCves[k], actual.ScannedCves[k]) {
|
||||
o := pp.Sprintf("%v", tt.out.ScannedCves[k])
|
||||
@@ -653,52 +460,52 @@ func TestIsDisplayUpdatableNum(t *testing.T) {
|
||||
},
|
||||
{
|
||||
mode: []byte{config.Fast},
|
||||
family: config.RedHat,
|
||||
family: constant.RedHat,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
mode: []byte{config.Fast},
|
||||
family: config.Oracle,
|
||||
family: constant.Oracle,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
mode: []byte{config.Fast},
|
||||
family: config.Debian,
|
||||
family: constant.Debian,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
mode: []byte{config.Fast},
|
||||
family: config.Ubuntu,
|
||||
family: constant.Ubuntu,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
mode: []byte{config.Fast},
|
||||
family: config.Raspbian,
|
||||
family: constant.Raspbian,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
mode: []byte{config.Fast},
|
||||
family: config.CentOS,
|
||||
family: constant.CentOS,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
mode: []byte{config.Fast},
|
||||
family: config.Amazon,
|
||||
family: constant.Amazon,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
mode: []byte{config.Fast},
|
||||
family: config.FreeBSD,
|
||||
family: constant.FreeBSD,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
mode: []byte{config.Fast},
|
||||
family: config.OpenSUSE,
|
||||
family: constant.OpenSUSE,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
mode: []byte{config.Fast},
|
||||
family: config.Alpine,
|
||||
family: constant.Alpine,
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
@@ -708,14 +515,11 @@ func TestIsDisplayUpdatableNum(t *testing.T) {
|
||||
for _, m := range tt.mode {
|
||||
mode.Set(m)
|
||||
}
|
||||
config.Conf.Servers = map[string]config.ServerInfo{
|
||||
"name": {Mode: mode},
|
||||
}
|
||||
r := ScanResult{
|
||||
ServerName: "name",
|
||||
Family: tt.family,
|
||||
}
|
||||
act := r.isDisplayUpdatableNum()
|
||||
act := r.isDisplayUpdatableNum(mode)
|
||||
if tt.expected != act {
|
||||
t.Errorf("[%d] expected %#v, actual %#v", i, tt.expected, act)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
exploitmodels "github.com/vulsio/go-exploitdb/models"
|
||||
)
|
||||
|
||||
@@ -78,19 +77,14 @@ func (v VulnInfos) CountGroupBySeverity() map[string]int {
|
||||
}
|
||||
|
||||
// FormatCveSummary summarize the number of CVEs group by CVSSv2 Severity
|
||||
func (v VulnInfos) FormatCveSummary() (line string) {
|
||||
func (v VulnInfos) FormatCveSummary() string {
|
||||
m := v.CountGroupBySeverity()
|
||||
if config.Conf.IgnoreUnscoredCves {
|
||||
line = fmt.Sprintf("Total: %d (Critical:%d High:%d Medium:%d Low:%d)",
|
||||
m["High"]+m["Medium"]+m["Low"], m["Critical"], m["High"], m["Medium"], m["Low"])
|
||||
} else {
|
||||
line = fmt.Sprintf("Total: %d (Critical:%d High:%d Medium:%d Low:%d ?:%d)",
|
||||
m["High"]+m["Medium"]+m["Low"]+m["Unknown"],
|
||||
m["Critical"], m["High"], m["Medium"], m["Low"], m["Unknown"])
|
||||
}
|
||||
line := fmt.Sprintf("Total: %d (Critical:%d High:%d Medium:%d Low:%d ?:%d)",
|
||||
m["High"]+m["Medium"]+m["Low"]+m["Unknown"],
|
||||
m["Critical"], m["High"], m["Medium"], m["Low"], m["Unknown"])
|
||||
|
||||
if config.Conf.DiffMinus || config.Conf.DiffPlus {
|
||||
nPlus, nMinus := v.CountDiff()
|
||||
nPlus, nMinus := v.CountDiff()
|
||||
if 0 < nPlus || 0 < nMinus {
|
||||
line = fmt.Sprintf("%s +%d -%d", line, nPlus, nMinus)
|
||||
}
|
||||
return line
|
||||
@@ -266,8 +260,8 @@ const (
|
||||
)
|
||||
|
||||
// CveIDDiffFormat format CVE-ID for diff mode
|
||||
func (v VulnInfo) CveIDDiffFormat(isDiffMode bool) string {
|
||||
if isDiffMode {
|
||||
func (v VulnInfo) CveIDDiffFormat() string {
|
||||
if v.DiffStatus != "" {
|
||||
return fmt.Sprintf("%s %s", v.DiffStatus, v.CveID)
|
||||
}
|
||||
return fmt.Sprintf("%s", v.CveID)
|
||||
|
||||
@@ -4,6 +4,7 @@ package oval
|
||||
|
||||
import (
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
"github.com/kotakanbe/goval-dictionary/db"
|
||||
@@ -18,7 +19,7 @@ type Alpine struct {
|
||||
func NewAlpine() Alpine {
|
||||
return Alpine{
|
||||
Base{
|
||||
family: config.Alpine,
|
||||
family: constant.Alpine,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
"github.com/kotakanbe/goval-dictionary/db"
|
||||
@@ -40,7 +41,7 @@ func (o DebianBase) update(r *models.ScanResult, defPacks defPacks) {
|
||||
defPacks.def.Debian.CveID)
|
||||
cveContents = models.CveContents{}
|
||||
}
|
||||
if r.Family != config.Raspbian {
|
||||
if r.Family != constant.Raspbian {
|
||||
vinfo.Confidences.AppendIfMissing(models.OvalMatch)
|
||||
} else {
|
||||
if len(vinfo.Confidences) == 0 {
|
||||
@@ -113,7 +114,7 @@ func NewDebian() Debian {
|
||||
return Debian{
|
||||
DebianBase{
|
||||
Base{
|
||||
family: config.Debian,
|
||||
family: constant.Debian,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -140,7 +141,7 @@ func (o Debian) FillWithOval(driver db.DB, r *models.ScanResult) (nCVEs int, err
|
||||
|
||||
var relatedDefs ovalResult
|
||||
if config.Conf.OvalDict.IsFetchViaHTTP() {
|
||||
if r.Family != config.Raspbian {
|
||||
if r.Family != constant.Raspbian {
|
||||
if relatedDefs, err = getDefsByPackNameViaHTTP(r); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -152,7 +153,7 @@ func (o Debian) FillWithOval(driver db.DB, r *models.ScanResult) (nCVEs int, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if r.Family != config.Raspbian {
|
||||
if r.Family != constant.Raspbian {
|
||||
if relatedDefs, err = getDefsByPackNameFromOvalDB(driver, r); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -203,7 +204,7 @@ func NewUbuntu() Ubuntu {
|
||||
return Ubuntu{
|
||||
DebianBase{
|
||||
Base{
|
||||
family: config.Ubuntu,
|
||||
family: constant.Ubuntu,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
"github.com/kotakanbe/goval-dictionary/db"
|
||||
@@ -143,7 +144,7 @@ func (o RedHatBase) update(r *models.ScanResult, defPacks defPacks) (nCVEs int)
|
||||
|
||||
func (o RedHatBase) convertToDistroAdvisory(def *ovalmodels.Definition) *models.DistroAdvisory {
|
||||
advisoryID := def.Title
|
||||
if (o.family == config.RedHat || o.family == config.CentOS) && len(advisoryID) > 0 {
|
||||
if (o.family == constant.RedHat || o.family == constant.CentOS) && len(advisoryID) > 0 {
|
||||
ss := strings.Fields(def.Title)
|
||||
advisoryID = strings.TrimSuffix(ss[0], ":")
|
||||
}
|
||||
@@ -250,7 +251,7 @@ func NewRedhat() RedHat {
|
||||
return RedHat{
|
||||
RedHatBase{
|
||||
Base{
|
||||
family: config.RedHat,
|
||||
family: constant.RedHat,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -266,7 +267,7 @@ func NewCentOS() CentOS {
|
||||
return CentOS{
|
||||
RedHatBase{
|
||||
Base{
|
||||
family: config.CentOS,
|
||||
family: constant.CentOS,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -282,7 +283,7 @@ func NewOracle() Oracle {
|
||||
return Oracle{
|
||||
RedHatBase{
|
||||
Base{
|
||||
family: config.Oracle,
|
||||
family: constant.Oracle,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -299,7 +300,7 @@ func NewAmazon() Amazon {
|
||||
return Amazon{
|
||||
RedHatBase{
|
||||
Base{
|
||||
family: config.Amazon,
|
||||
family: constant.Amazon,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ package oval
|
||||
|
||||
import (
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
"github.com/kotakanbe/goval-dictionary/db"
|
||||
@@ -20,7 +21,7 @@ func NewSUSE() SUSE {
|
||||
// TODO implement other family
|
||||
return SUSE{
|
||||
Base{
|
||||
family: config.SUSEEnterpriseServer,
|
||||
family: constant.SUSEEnterpriseServer,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
33
oval/util.go
33
oval/util.go
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
apkver "github.com/knqyf263/go-apk-version"
|
||||
@@ -300,7 +301,7 @@ func isOvalDefAffected(def ovalmodels.Definition, req request, family string, ru
|
||||
|
||||
if running.Release != "" {
|
||||
switch family {
|
||||
case config.RedHat, config.CentOS:
|
||||
case constant.RedHat, constant.CentOS:
|
||||
// For kernel related packages, ignore OVAL information with different major versions
|
||||
if _, ok := kernelRelatedPackNames[ovalPack.Name]; ok {
|
||||
if util.Major(ovalPack.Version) != util.Major(running.Release) {
|
||||
@@ -329,12 +330,12 @@ func isOvalDefAffected(def ovalmodels.Definition, req request, family string, ru
|
||||
|
||||
// If the version of installed is less than in OVAL
|
||||
switch family {
|
||||
case config.RedHat,
|
||||
config.Amazon,
|
||||
config.SUSEEnterpriseServer,
|
||||
config.Debian,
|
||||
config.Ubuntu,
|
||||
config.Raspbian:
|
||||
case constant.RedHat,
|
||||
constant.Amazon,
|
||||
constant.SUSEEnterpriseServer,
|
||||
constant.Debian,
|
||||
constant.Ubuntu,
|
||||
constant.Raspbian:
|
||||
// Use fixed state in OVAL for these distros.
|
||||
return true, false, ovalPack.Version
|
||||
}
|
||||
@@ -365,9 +366,9 @@ func isOvalDefAffected(def ovalmodels.Definition, req request, family string, ru
|
||||
|
||||
func lessThan(family, newVer string, packInOVAL ovalmodels.Package) (bool, error) {
|
||||
switch family {
|
||||
case config.Debian,
|
||||
config.Ubuntu,
|
||||
config.Raspbian:
|
||||
case constant.Debian,
|
||||
constant.Ubuntu,
|
||||
constant.Raspbian:
|
||||
vera, err := debver.NewVersion(newVer)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -378,7 +379,7 @@ func lessThan(family, newVer string, packInOVAL ovalmodels.Package) (bool, error
|
||||
}
|
||||
return vera.LessThan(verb), nil
|
||||
|
||||
case config.Alpine:
|
||||
case constant.Alpine:
|
||||
vera, err := apkver.NewVersion(newVer)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -389,15 +390,15 @@ func lessThan(family, newVer string, packInOVAL ovalmodels.Package) (bool, error
|
||||
}
|
||||
return vera.LessThan(verb), nil
|
||||
|
||||
case config.Oracle,
|
||||
config.SUSEEnterpriseServer,
|
||||
config.Amazon:
|
||||
case constant.Oracle,
|
||||
constant.SUSEEnterpriseServer,
|
||||
constant.Amazon:
|
||||
vera := rpmver.NewVersion(newVer)
|
||||
verb := rpmver.NewVersion(packInOVAL.Version)
|
||||
return vera.LessThan(verb), nil
|
||||
|
||||
case config.RedHat,
|
||||
config.CentOS:
|
||||
case constant.RedHat,
|
||||
constant.CentOS:
|
||||
vera := rpmver.NewVersion(centOSVersionToRHEL(newVer))
|
||||
verb := rpmver.NewVersion(centOSVersionToRHEL(packInOVAL.Version))
|
||||
return vera.LessThan(verb), nil
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/models"
|
||||
ovalmodels "github.com/kotakanbe/goval-dictionary/models"
|
||||
)
|
||||
@@ -1030,7 +1030,7 @@ func TestIsOvalDefAffected(t *testing.T) {
|
||||
// For kernel related packages, ignore OVAL with different major versions
|
||||
{
|
||||
in: in{
|
||||
family: config.CentOS,
|
||||
family: constant.CentOS,
|
||||
def: ovalmodels.Definition{
|
||||
AffectedPacks: []ovalmodels.Package{
|
||||
{
|
||||
@@ -1054,7 +1054,7 @@ func TestIsOvalDefAffected(t *testing.T) {
|
||||
},
|
||||
{
|
||||
in: in{
|
||||
family: config.CentOS,
|
||||
family: constant.CentOS,
|
||||
def: ovalmodels.Definition{
|
||||
AffectedPacks: []ovalmodels.Package{
|
||||
{
|
||||
@@ -1080,7 +1080,7 @@ func TestIsOvalDefAffected(t *testing.T) {
|
||||
// dnf module
|
||||
{
|
||||
in: in{
|
||||
family: config.RedHat,
|
||||
family: constant.RedHat,
|
||||
def: ovalmodels.Definition{
|
||||
AffectedPacks: []ovalmodels.Package{
|
||||
{
|
||||
@@ -1106,7 +1106,7 @@ func TestIsOvalDefAffected(t *testing.T) {
|
||||
// dnf module 2
|
||||
{
|
||||
in: in{
|
||||
family: config.RedHat,
|
||||
family: constant.RedHat,
|
||||
def: ovalmodels.Definition{
|
||||
AffectedPacks: []ovalmodels.Package{
|
||||
{
|
||||
@@ -1131,7 +1131,7 @@ func TestIsOvalDefAffected(t *testing.T) {
|
||||
// dnf module 3
|
||||
{
|
||||
in: in{
|
||||
family: config.RedHat,
|
||||
family: constant.RedHat,
|
||||
def: ovalmodels.Definition{
|
||||
AffectedPacks: []ovalmodels.Package{
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package report
|
||||
package reporter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -14,7 +14,13 @@ import (
|
||||
)
|
||||
|
||||
// AzureBlobWriter writes results to AzureBlob
|
||||
type AzureBlobWriter struct{}
|
||||
type AzureBlobWriter struct {
|
||||
FormatJSON bool
|
||||
FormatFullText bool
|
||||
FormatOneLineText bool
|
||||
FormatList bool
|
||||
Gzip bool
|
||||
}
|
||||
|
||||
// Write results to Azure Blob storage
|
||||
func (w AzureBlobWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
@@ -27,41 +33,41 @@ func (w AzureBlobWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.Conf.FormatOneLineText {
|
||||
if w.FormatOneLineText {
|
||||
timestr := rs[0].ScannedAt.Format(time.RFC3339)
|
||||
k := fmt.Sprintf(timestr + "/summary.txt")
|
||||
text := formatOneLineSummary(rs...)
|
||||
b := []byte(text)
|
||||
if err := createBlockBlob(cli, k, b); err != nil {
|
||||
if err := createBlockBlob(cli, k, b, w.Gzip); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, r := range rs {
|
||||
key := r.ReportKeyName()
|
||||
if c.Conf.FormatJSON {
|
||||
if w.FormatJSON {
|
||||
k := key + ".json"
|
||||
var b []byte
|
||||
if b, err = json.Marshal(r); err != nil {
|
||||
return xerrors.Errorf("Failed to Marshal to JSON: %w", err)
|
||||
}
|
||||
if err := createBlockBlob(cli, k, b); err != nil {
|
||||
if err := createBlockBlob(cli, k, b, w.Gzip); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if c.Conf.FormatList {
|
||||
if w.FormatList {
|
||||
k := key + "_short.txt"
|
||||
b := []byte(formatList(r))
|
||||
if err := createBlockBlob(cli, k, b); err != nil {
|
||||
if err := createBlockBlob(cli, k, b, w.Gzip); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if c.Conf.FormatFullText {
|
||||
if w.FormatFullText {
|
||||
k := key + "_full.txt"
|
||||
b := []byte(formatFullPlainText(r))
|
||||
if err := createBlockBlob(cli, k, b); err != nil {
|
||||
if err := createBlockBlob(cli, k, b, w.Gzip); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -101,9 +107,9 @@ func getBlobClient() (storage.BlobStorageClient, error) {
|
||||
return api.GetBlobService(), nil
|
||||
}
|
||||
|
||||
func createBlockBlob(cli storage.BlobStorageClient, k string, b []byte) error {
|
||||
func createBlockBlob(cli storage.BlobStorageClient, k string, b []byte, gzip bool) error {
|
||||
var err error
|
||||
if c.Conf.GZIP {
|
||||
if gzip {
|
||||
if b, err = gz(b); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package report
|
||||
package reporter
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -38,7 +38,7 @@ func (w ChatWorkWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
vinfo.CveID,
|
||||
strconv.FormatFloat(maxCvss.Value.Score, 'f', 1, 64),
|
||||
severity,
|
||||
vinfo.Summaries(config.Conf.Lang, r.Family)[0].Value)
|
||||
vinfo.Summaries(r.Lang, r.Family)[0].Value)
|
||||
|
||||
if err = chatWorkpostMessage(conf.Room, conf.APIToken, message); err != nil {
|
||||
return err
|
||||
@@ -1,4 +1,4 @@
|
||||
package report
|
||||
package reporter
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
@@ -16,7 +16,11 @@ import (
|
||||
)
|
||||
|
||||
// EMailWriter send mail
|
||||
type EMailWriter struct{}
|
||||
type EMailWriter struct {
|
||||
FormatOneEMail bool
|
||||
FormatOneLineText bool
|
||||
FormatList bool
|
||||
}
|
||||
|
||||
func (w EMailWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
conf := config.Conf
|
||||
@@ -24,7 +28,7 @@ func (w EMailWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
sender := NewEMailSender()
|
||||
m := map[string]int{}
|
||||
for _, r := range rs {
|
||||
if conf.FormatOneEMail {
|
||||
if w.FormatOneEMail {
|
||||
message += formatFullPlainText(r) + "\r\n\r\n"
|
||||
mm := r.ScannedCves.CountGroupBySeverity()
|
||||
keys := []string{"High", "Medium", "Low", "Unknown"}
|
||||
@@ -42,12 +46,12 @@ func (w EMailWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
r.ServerInfo(),
|
||||
r.ScannedCves.FormatCveSummary())
|
||||
}
|
||||
if conf.FormatList {
|
||||
if w.FormatList {
|
||||
message = formatList(r)
|
||||
} else {
|
||||
message = formatFullPlainText(r)
|
||||
}
|
||||
if conf.FormatOneLineText {
|
||||
if w.FormatOneLineText {
|
||||
message = fmt.Sprintf("One Line Summary\r\n================\r\n%s", formatOneLineSummary(r))
|
||||
}
|
||||
if err := sender.Send(subject, message); err != nil {
|
||||
@@ -55,19 +59,15 @@ func (w EMailWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
var summary string
|
||||
if config.Conf.IgnoreUnscoredCves {
|
||||
summary = fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d)",
|
||||
m["High"]+m["Medium"]+m["Low"], m["High"], m["Medium"], m["Low"])
|
||||
} else {
|
||||
summary = fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d ?:%d)",
|
||||
m["High"]+m["Medium"]+m["Low"]+m["Unknown"],
|
||||
m["High"], m["Medium"], m["Low"], m["Unknown"])
|
||||
}
|
||||
|
||||
summary := fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d ?:%d)",
|
||||
m["High"]+m["Medium"]+m["Low"]+m["Unknown"],
|
||||
m["High"], m["Medium"], m["Low"], m["Unknown"])
|
||||
|
||||
origmessage := message
|
||||
if conf.FormatOneEMail {
|
||||
if w.FormatOneEMail {
|
||||
message = fmt.Sprintf("One Line Summary\r\n================\r\n%s", formatOneLineSummary(rs...))
|
||||
if !conf.FormatOneLineText {
|
||||
if !w.FormatOneLineText {
|
||||
message += fmt.Sprintf("\r\n\r\n%s", origmessage)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package report
|
||||
package reporter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -1,4 +1,4 @@
|
||||
package report
|
||||
package reporter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -6,21 +6,28 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
c "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// LocalFileWriter writes results to a local file.
|
||||
type LocalFileWriter struct {
|
||||
CurrentDir string
|
||||
CurrentDir string
|
||||
DiffPlus bool
|
||||
DiffMinus bool
|
||||
FormatJSON bool
|
||||
FormatCsv bool
|
||||
FormatFullText bool
|
||||
FormatOneLineText bool
|
||||
FormatList bool
|
||||
Gzip bool
|
||||
}
|
||||
|
||||
func (w LocalFileWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
if c.Conf.FormatOneLineText {
|
||||
if w.FormatOneLineText {
|
||||
path := filepath.Join(w.CurrentDir, "summary.txt")
|
||||
text := formatOneLineSummary(rs...)
|
||||
if err := writeFile(path, []byte(text), 0600); err != nil {
|
||||
if err := w.writeFile(path, []byte(text), 0600); err != nil {
|
||||
return xerrors.Errorf(
|
||||
"Failed to write to file. path: %s, err: %w",
|
||||
path, err)
|
||||
@@ -30,48 +37,48 @@ func (w LocalFileWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
for _, r := range rs {
|
||||
path := filepath.Join(w.CurrentDir, r.ReportFileName())
|
||||
|
||||
if c.Conf.FormatJSON {
|
||||
if w.FormatJSON {
|
||||
p := path + ".json"
|
||||
if c.Conf.DiffPlus || c.Conf.DiffMinus {
|
||||
if w.DiffPlus || w.DiffMinus {
|
||||
p = path + "_diff.json"
|
||||
}
|
||||
var b []byte
|
||||
if b, err = json.MarshalIndent(r, "", " "); err != nil {
|
||||
return xerrors.Errorf("Failed to Marshal to JSON: %w", err)
|
||||
}
|
||||
if err := writeFile(p, b, 0600); err != nil {
|
||||
if err := w.writeFile(p, b, 0600); err != nil {
|
||||
return xerrors.Errorf("Failed to write JSON. path: %s, err: %w", p, err)
|
||||
}
|
||||
}
|
||||
|
||||
if c.Conf.FormatList {
|
||||
if w.FormatList {
|
||||
p := path + "_short.txt"
|
||||
if c.Conf.DiffPlus || c.Conf.DiffMinus {
|
||||
if w.DiffPlus || w.DiffMinus {
|
||||
p = path + "_short_diff.txt"
|
||||
}
|
||||
if err := writeFile(
|
||||
if err := w.writeFile(
|
||||
p, []byte(formatList(r)), 0600); err != nil {
|
||||
return xerrors.Errorf(
|
||||
"Failed to write text files. path: %s, err: %w", p, err)
|
||||
}
|
||||
}
|
||||
|
||||
if c.Conf.FormatFullText {
|
||||
if w.FormatFullText {
|
||||
p := path + "_full.txt"
|
||||
if c.Conf.DiffPlus || c.Conf.DiffMinus {
|
||||
if w.DiffPlus || w.DiffMinus {
|
||||
p = path + "_full_diff.txt"
|
||||
}
|
||||
|
||||
if err := writeFile(
|
||||
if err := w.writeFile(
|
||||
p, []byte(formatFullPlainText(r)), 0600); err != nil {
|
||||
return xerrors.Errorf(
|
||||
"Failed to write text files. path: %s, err: %w", p, err)
|
||||
}
|
||||
}
|
||||
|
||||
if c.Conf.FormatCsvList {
|
||||
if w.FormatCsv {
|
||||
p := path + ".csv"
|
||||
if c.Conf.DiffPlus || c.Conf.DiffMinus {
|
||||
if w.DiffPlus || w.DiffMinus {
|
||||
p = path + "_diff.csv"
|
||||
}
|
||||
if err := formatCsvList(r, p); err != nil {
|
||||
@@ -83,10 +90,10 @@ func (w LocalFileWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeFile(path string, data []byte, perm os.FileMode) error {
|
||||
var err error
|
||||
if c.Conf.GZIP {
|
||||
if data, err = gz(data); err != nil {
|
||||
func (w LocalFileWriter) writeFile(path string, data []byte, perm os.FileMode) (err error) {
|
||||
if w.Gzip {
|
||||
data, err = gz(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path += ".gz"
|
||||
@@ -1,4 +1,4 @@
|
||||
package report
|
||||
package reporter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -20,7 +20,13 @@ import (
|
||||
)
|
||||
|
||||
// S3Writer writes results to S3
|
||||
type S3Writer struct{}
|
||||
type S3Writer struct {
|
||||
FormatJSON bool
|
||||
FormatFullText bool
|
||||
FormatOneLineText bool
|
||||
FormatList bool
|
||||
Gzip bool
|
||||
}
|
||||
|
||||
func getS3() (*s3.S3, error) {
|
||||
ses, err := session.NewSession()
|
||||
@@ -54,40 +60,40 @@ func (w S3Writer) Write(rs ...models.ScanResult) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.Conf.FormatOneLineText {
|
||||
if w.FormatOneLineText {
|
||||
timestr := rs[0].ScannedAt.Format(time.RFC3339)
|
||||
k := fmt.Sprintf(timestr + "/summary.txt")
|
||||
text := formatOneLineSummary(rs...)
|
||||
if err := putObject(svc, k, []byte(text)); err != nil {
|
||||
if err := putObject(svc, k, []byte(text), w.Gzip); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, r := range rs {
|
||||
key := r.ReportKeyName()
|
||||
if c.Conf.FormatJSON {
|
||||
if w.FormatJSON {
|
||||
k := key + ".json"
|
||||
var b []byte
|
||||
if b, err = json.Marshal(r); err != nil {
|
||||
return xerrors.Errorf("Failed to Marshal to JSON: %w", err)
|
||||
}
|
||||
if err := putObject(svc, k, b); err != nil {
|
||||
if err := putObject(svc, k, b, w.Gzip); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if c.Conf.FormatList {
|
||||
if w.FormatList {
|
||||
k := key + "_short.txt"
|
||||
text := formatList(r)
|
||||
if err := putObject(svc, k, []byte(text)); err != nil {
|
||||
if err := putObject(svc, k, []byte(text), w.Gzip); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if c.Conf.FormatFullText {
|
||||
if w.FormatFullText {
|
||||
k := key + "_full.txt"
|
||||
text := formatFullPlainText(r)
|
||||
if err := putObject(svc, k, []byte(text)); err != nil {
|
||||
if err := putObject(svc, k, []byte(text), w.Gzip); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -124,9 +130,9 @@ func CheckIfBucketExists() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func putObject(svc *s3.S3, k string, b []byte) error {
|
||||
func putObject(svc *s3.S3, k string, b []byte, gzip bool) error {
|
||||
var err error
|
||||
if c.Conf.GZIP {
|
||||
if gzip {
|
||||
if b, err = gz(b); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package report
|
||||
package reporter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -16,6 +16,13 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// SlackWriter send report to slack
|
||||
type SlackWriter struct {
|
||||
FormatOneLineText bool
|
||||
lang string
|
||||
osFamily string
|
||||
}
|
||||
|
||||
type message struct {
|
||||
Text string `json:"text"`
|
||||
Username string `json:"username"`
|
||||
@@ -24,15 +31,13 @@ type message struct {
|
||||
Attachments []slack.Attachment `json:"attachments"`
|
||||
}
|
||||
|
||||
// SlackWriter send report to slack
|
||||
type SlackWriter struct{}
|
||||
|
||||
func (w SlackWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
conf := config.Conf.Slack
|
||||
channel := conf.Channel
|
||||
token := conf.LegacyToken
|
||||
|
||||
for _, r := range rs {
|
||||
w.lang, w.osFamily = r.Lang, r.Family
|
||||
if channel == "${servername}" {
|
||||
channel = fmt.Sprintf("#%s", r.ServerName)
|
||||
}
|
||||
@@ -42,7 +47,7 @@ func (w SlackWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
// https://api.slack.com/methods/chat.postMessage
|
||||
maxAttachments := 100
|
||||
m := map[int][]slack.Attachment{}
|
||||
for i, a := range toSlackAttachments(r) {
|
||||
for i, a := range w.toSlackAttachments(r) {
|
||||
m[i/maxAttachments] = append(m[i/maxAttachments], a)
|
||||
}
|
||||
chunkKeys := []int{}
|
||||
@@ -52,7 +57,7 @@ func (w SlackWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
sort.Ints(chunkKeys)
|
||||
|
||||
summary := fmt.Sprintf("%s\n%s",
|
||||
getNotifyUsers(config.Conf.Slack.NotifyUsers),
|
||||
w.getNotifyUsers(config.Conf.Slack.NotifyUsers),
|
||||
formatOneLineSummary(r))
|
||||
|
||||
// Send slack by API
|
||||
@@ -72,7 +77,7 @@ func (w SlackWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
if config.Conf.FormatOneLineText || 0 < len(r.Errors) {
|
||||
if w.FormatOneLineText || 0 < len(r.Errors) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -98,11 +103,11 @@ func (w SlackWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
IconEmoji: conf.IconEmoji,
|
||||
Channel: channel,
|
||||
}
|
||||
if err := send(msg); err != nil {
|
||||
if err := w.send(msg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if config.Conf.FormatOneLineText || 0 < len(r.Errors) {
|
||||
if w.FormatOneLineText || 0 < len(r.Errors) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -119,7 +124,7 @@ func (w SlackWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
Channel: channel,
|
||||
Attachments: m[k],
|
||||
}
|
||||
if err = send(msg); err != nil {
|
||||
if err = w.send(msg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -128,7 +133,7 @@ func (w SlackWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func send(msg message) error {
|
||||
func (w SlackWriter) send(msg message) error {
|
||||
conf := config.Conf.Slack
|
||||
count, retryMax := 0, 10
|
||||
|
||||
@@ -162,7 +167,7 @@ func send(msg message) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func toSlackAttachments(r models.ScanResult) (attaches []slack.Attachment) {
|
||||
func (w SlackWriter) toSlackAttachments(r models.ScanResult) (attaches []slack.Attachment) {
|
||||
vinfos := r.ScannedCves.ToSortedSlice()
|
||||
for _, vinfo := range vinfos {
|
||||
|
||||
@@ -206,9 +211,9 @@ func toSlackAttachments(r models.ScanResult) (attaches []slack.Attachment) {
|
||||
}
|
||||
|
||||
a := slack.Attachment{
|
||||
Title: vinfo.CveIDDiffFormat(config.Conf.DiffMinus || config.Conf.DiffPlus),
|
||||
Title: vinfo.CveIDDiffFormat(),
|
||||
TitleLink: "https://nvd.nist.gov/vuln/detail/" + vinfo.CveID,
|
||||
Text: attachmentText(vinfo, r.Family, r.CweDict, r.Packages),
|
||||
Text: w.attachmentText(vinfo, r.CweDict, r.Packages),
|
||||
MarkdownIn: []string{"text", "pretext"},
|
||||
Fields: []slack.AttachmentField{
|
||||
{
|
||||
@@ -244,7 +249,7 @@ func cvssColor(cvssScore float64) string {
|
||||
}
|
||||
}
|
||||
|
||||
func attachmentText(vinfo models.VulnInfo, osFamily string, cweDict map[string]models.CweDictEntry, packs models.Packages) string {
|
||||
func (w SlackWriter) attachmentText(vinfo models.VulnInfo, cweDict map[string]models.CweDictEntry, packs models.Packages) string {
|
||||
maxCvss := vinfo.MaxCvssScore()
|
||||
vectors := []string{}
|
||||
|
||||
@@ -277,7 +282,7 @@ func attachmentText(vinfo models.VulnInfo, osFamily string, cweDict map[string]m
|
||||
} else {
|
||||
if 0 < len(vinfo.DistroAdvisories) {
|
||||
links := []string{}
|
||||
for _, v := range vinfo.CveContents.PrimarySrcURLs(config.Conf.Lang, osFamily, vinfo.CveID) {
|
||||
for _, v := range vinfo.CveContents.PrimarySrcURLs(w.lang, w.osFamily, vinfo.CveID) {
|
||||
links = append(links, fmt.Sprintf("<%s|%s>", v.Value, v.Type))
|
||||
}
|
||||
|
||||
@@ -312,16 +317,16 @@ func attachmentText(vinfo models.VulnInfo, osFamily string, cweDict map[string]m
|
||||
nwvec,
|
||||
vinfo.PatchStatus(packs),
|
||||
strings.Join(vectors, "\n"),
|
||||
vinfo.Summaries(config.Conf.Lang, osFamily)[0].Value,
|
||||
vinfo.Summaries(w.lang, w.osFamily)[0].Value,
|
||||
mitigation,
|
||||
cweIDs(vinfo, osFamily, cweDict),
|
||||
w.cweIDs(vinfo, w.osFamily, cweDict),
|
||||
)
|
||||
}
|
||||
|
||||
func cweIDs(vinfo models.VulnInfo, osFamily string, cweDict models.CweDict) string {
|
||||
func (w SlackWriter) cweIDs(vinfo models.VulnInfo, osFamily string, cweDict models.CweDict) string {
|
||||
links := []string{}
|
||||
for _, c := range vinfo.CveContents.UniqCweIDs(osFamily) {
|
||||
name, url, top10Rank, top10URL, cweTop25Rank, cweTop25URL, sansTop25Rank, sansTop25URL := cweDict.Get(c.Value, osFamily)
|
||||
name, url, top10Rank, top10URL, cweTop25Rank, cweTop25URL, sansTop25Rank, sansTop25URL := cweDict.Get(c.Value, w.lang)
|
||||
line := ""
|
||||
if top10Rank != "" {
|
||||
line = fmt.Sprintf("<%s|[OWASP Top %s]>",
|
||||
@@ -344,7 +349,7 @@ func cweIDs(vinfo models.VulnInfo, osFamily string, cweDict models.CweDict) stri
|
||||
}
|
||||
|
||||
// See testcase
|
||||
func getNotifyUsers(notifyUsers []string) string {
|
||||
func (w SlackWriter) getNotifyUsers(notifyUsers []string) string {
|
||||
slackStyleTexts := []string{}
|
||||
for _, username := range notifyUsers {
|
||||
slackStyleTexts = append(slackStyleTexts, fmt.Sprintf("<%s>", username))
|
||||
@@ -1,4 +1,4 @@
|
||||
package report
|
||||
package reporter
|
||||
|
||||
import "testing"
|
||||
|
||||
@@ -14,7 +14,7 @@ func TestGetNotifyUsers(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
actual := getNotifyUsers(tt.in)
|
||||
actual := SlackWriter{}.getNotifyUsers(tt.in)
|
||||
if tt.expected != actual {
|
||||
t.Errorf("expected %s, actual %s", tt.expected, actual)
|
||||
}
|
||||
@@ -1,14 +1,20 @@
|
||||
package report
|
||||
package reporter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
c "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
)
|
||||
|
||||
// StdoutWriter write to stdout
|
||||
type StdoutWriter struct{}
|
||||
type StdoutWriter struct {
|
||||
FormatCsv bool
|
||||
FormatFullText bool
|
||||
FormatOneLineText bool
|
||||
FormatList bool
|
||||
}
|
||||
|
||||
//TODO support -format-jSON
|
||||
|
||||
// WriteScanSummary prints Scan summary at the end of scan
|
||||
func (w StdoutWriter) WriteScanSummary(rs ...models.ScanResult) {
|
||||
@@ -19,7 +25,7 @@ func (w StdoutWriter) WriteScanSummary(rs ...models.ScanResult) {
|
||||
}
|
||||
|
||||
func (w StdoutWriter) Write(rs ...models.ScanResult) error {
|
||||
if c.Conf.FormatOneLineText {
|
||||
if w.FormatOneLineText {
|
||||
fmt.Print("\n\n")
|
||||
fmt.Println("One Line Summary")
|
||||
fmt.Println("================")
|
||||
@@ -27,13 +33,13 @@ func (w StdoutWriter) Write(rs ...models.ScanResult) error {
|
||||
fmt.Print("\n")
|
||||
}
|
||||
|
||||
if c.Conf.FormatList || c.Conf.FormatCsvList {
|
||||
if w.FormatList || w.FormatCsv {
|
||||
for _, r := range rs {
|
||||
fmt.Println(formatList(r))
|
||||
}
|
||||
}
|
||||
|
||||
if c.Conf.FormatFullText {
|
||||
if w.FormatFullText {
|
||||
for _, r := range rs {
|
||||
fmt.Println(formatFullPlainText(r))
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package report
|
||||
package reporter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
package report
|
||||
package reporter
|
||||
|
||||
import (
|
||||
"sort"
|
||||
@@ -1,4 +1,4 @@
|
||||
package report
|
||||
package reporter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -25,7 +25,7 @@ func (w TelegramWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
r.ServerInfo(),
|
||||
r.ScannedCves.FormatCveSummary(),
|
||||
r.ScannedCves.FormatFixedStatus(r.Packages),
|
||||
r.FormatUpdatablePacksSummary())}
|
||||
r.FormatUpdatablePkgsSummary())}
|
||||
for _, vinfo := range r.ScannedCves {
|
||||
maxCvss := vinfo.MaxCvssScore()
|
||||
severity := strings.ToUpper(maxCvss.Value.Severity)
|
||||
@@ -38,7 +38,7 @@ func (w TelegramWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
strconv.FormatFloat(maxCvss.Value.Score, 'f', 1, 64),
|
||||
severity,
|
||||
maxCvss.Value.Vector,
|
||||
vinfo.Summaries(config.Conf.Lang, r.Family)[0].Value))
|
||||
vinfo.Summaries(r.Lang, r.Family)[0].Value))
|
||||
if len(msgs) == 5 {
|
||||
if err = sendMessage(conf.ChatID, conf.Token, strings.Join(msgs, "\n\n")); err != nil {
|
||||
return err
|
||||
@@ -1,4 +1,4 @@
|
||||
package report
|
||||
package reporter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -23,9 +22,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
vulsOpenTag = "<vulsreport>"
|
||||
vulsCloseTag = "</vulsreport>"
|
||||
maxColWidth = 100
|
||||
maxColWidth = 100
|
||||
)
|
||||
|
||||
func formatScanSummary(rs ...models.ScanResult) string {
|
||||
@@ -40,7 +37,7 @@ func formatScanSummary(rs ...models.ScanResult) string {
|
||||
cols = []interface{}{
|
||||
r.FormatServerName(),
|
||||
fmt.Sprintf("%s%s", r.Family, r.Release),
|
||||
r.FormatUpdatablePacksSummary(),
|
||||
r.FormatUpdatablePkgsSummary(),
|
||||
}
|
||||
if 0 < len(r.WordPressPackages) {
|
||||
cols = append(cols, fmt.Sprintf("%d WordPress pkgs", len(r.WordPressPackages)))
|
||||
@@ -79,7 +76,7 @@ func formatOneLineSummary(rs ...models.ScanResult) string {
|
||||
r.FormatServerName(),
|
||||
r.ScannedCves.FormatCveSummary(),
|
||||
r.ScannedCves.FormatFixedStatus(r.Packages),
|
||||
r.FormatUpdatablePacksSummary(),
|
||||
r.FormatUpdatablePkgsSummary(),
|
||||
r.FormatExploitCveSummary(),
|
||||
r.FormatMetasploitCveSummary(),
|
||||
r.FormatAlertSummary(),
|
||||
@@ -124,7 +121,7 @@ func formatList(r models.ScanResult) string {
|
||||
%s
|
||||
No CVE-IDs are found in updatable packages.
|
||||
%s
|
||||
`, header, r.FormatUpdatablePacksSummary())
|
||||
`, header, r.FormatUpdatablePkgsSummary())
|
||||
}
|
||||
|
||||
data := [][]string{}
|
||||
@@ -149,7 +146,7 @@ No CVE-IDs are found in updatable packages.
|
||||
}
|
||||
|
||||
data = append(data, []string{
|
||||
vinfo.CveIDDiffFormat(config.Conf.DiffMinus || config.Conf.DiffPlus),
|
||||
vinfo.CveIDDiffFormat(),
|
||||
fmt.Sprintf("%4.1f", max),
|
||||
fmt.Sprintf("%5s", vinfo.AttackVector()),
|
||||
// fmt.Sprintf("%4.1f", v2max),
|
||||
@@ -199,7 +196,7 @@ func formatFullPlainText(r models.ScanResult) (lines string) {
|
||||
%s
|
||||
No CVE-IDs are found in updatable packages.
|
||||
%s
|
||||
`, header, r.FormatUpdatablePacksSummary())
|
||||
`, header, r.FormatUpdatablePkgsSummary())
|
||||
}
|
||||
|
||||
lines = header + "\n"
|
||||
@@ -220,14 +217,13 @@ No CVE-IDs are found in updatable packages.
|
||||
}
|
||||
|
||||
data = append(data, []string{"Summary", vuln.Summaries(
|
||||
config.Conf.Lang, r.Family)[0].Value})
|
||||
r.Lang, r.Family)[0].Value})
|
||||
|
||||
for _, m := range vuln.Mitigations {
|
||||
data = append(data, []string{"Mitigation", m.URL})
|
||||
}
|
||||
|
||||
links := vuln.CveContents.PrimarySrcURLs(
|
||||
config.Conf.Lang, r.Family, vuln.CveID)
|
||||
links := vuln.CveContents.PrimarySrcURLs(r.Lang, r.Family, vuln.CveID)
|
||||
for _, link := range links {
|
||||
data = append(data, []string{"Primary Src", link.Value})
|
||||
}
|
||||
@@ -373,7 +369,7 @@ No CVE-IDs are found in updatable packages.
|
||||
table.SetColWidth(80)
|
||||
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
|
||||
table.SetHeader([]string{
|
||||
vuln.CveIDDiffFormat(config.Conf.DiffMinus || config.Conf.DiffPlus),
|
||||
vuln.CveIDDiffFormat(),
|
||||
vuln.PatchStatus(r.Packages),
|
||||
})
|
||||
table.SetBorder(true)
|
||||
@@ -432,97 +428,17 @@ func cweJvnURL(cweID string) string {
|
||||
return fmt.Sprintf("http://jvndb.jvn.jp/ja/cwe/%s.html", cweID)
|
||||
}
|
||||
|
||||
func formatChangelogs(r models.ScanResult) string {
|
||||
buf := []string{}
|
||||
for _, p := range r.Packages {
|
||||
if p.NewVersion == "" {
|
||||
continue
|
||||
}
|
||||
clog := p.FormatChangelog()
|
||||
buf = append(buf, clog, "\n\n")
|
||||
func OverwriteJSONFile(dir string, r models.ScanResult) error {
|
||||
w := LocalFileWriter{
|
||||
CurrentDir: dir,
|
||||
FormatJSON: true,
|
||||
}
|
||||
return strings.Join(buf, "\n")
|
||||
}
|
||||
|
||||
func reuseScannedCves(r *models.ScanResult) bool {
|
||||
switch r.Family {
|
||||
case
|
||||
config.FreeBSD,
|
||||
config.Raspbian:
|
||||
return true
|
||||
}
|
||||
if isTrivyResult(r) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isTrivyResult(r *models.ScanResult) bool {
|
||||
_, ok := r.Optional["trivy-target"]
|
||||
return ok
|
||||
}
|
||||
|
||||
func needToRefreshCve(r models.ScanResult) bool {
|
||||
if r.Lang != config.Conf.Lang {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, cve := range r.ScannedCves {
|
||||
if 0 < len(cve.CveContents) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func overwriteJSONFile(dir string, r models.ScanResult) error {
|
||||
before := config.Conf.FormatJSON
|
||||
beforePlusDiff := config.Conf.DiffPlus
|
||||
beforeMinusDiff := config.Conf.DiffMinus
|
||||
config.Conf.FormatJSON = true
|
||||
config.Conf.DiffPlus = false
|
||||
config.Conf.DiffMinus = false
|
||||
w := LocalFileWriter{CurrentDir: dir}
|
||||
if err := w.Write(r); err != nil {
|
||||
return xerrors.Errorf("Failed to write summary report: %w", err)
|
||||
}
|
||||
config.Conf.FormatJSON = before
|
||||
config.Conf.DiffPlus = beforePlusDiff
|
||||
config.Conf.DiffMinus = beforeMinusDiff
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadPrevious(currs models.ScanResults) (prevs models.ScanResults, err error) {
|
||||
dirs, err := ListValidJSONDirs()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, result := range currs {
|
||||
filename := result.ServerName + ".json"
|
||||
if result.Container.Name != "" {
|
||||
filename = fmt.Sprintf("%s@%s.json", result.Container.Name, result.ServerName)
|
||||
}
|
||||
for _, dir := range dirs[1:] {
|
||||
path := filepath.Join(dir, filename)
|
||||
r, err := loadOneServerScanResult(path)
|
||||
if err != nil {
|
||||
util.Log.Debugf("%+v", err)
|
||||
continue
|
||||
}
|
||||
if r.Family == result.Family && r.Release == result.Release {
|
||||
prevs = append(prevs, *r)
|
||||
util.Log.Infof("Previous json found: %s", path)
|
||||
break
|
||||
} else {
|
||||
util.Log.Infof("Previous json is different family.Release: %s, pre: %s.%s cur: %s.%s",
|
||||
path, r.Family, r.Release, result.Family, result.Release)
|
||||
}
|
||||
}
|
||||
}
|
||||
return prevs, nil
|
||||
}
|
||||
|
||||
func diff(curResults, preResults models.ScanResults, isPlus, isMinus bool) (diffed models.ScanResults) {
|
||||
for _, current := range curResults {
|
||||
found := false
|
||||
@@ -637,21 +553,6 @@ func getMinusDiffCves(previous, current models.ScanResult) models.VulnInfos {
|
||||
return clear
|
||||
}
|
||||
|
||||
func isCveFixed(current models.VulnInfo, previous models.ScanResult) bool {
|
||||
preVinfo, _ := previous.ScannedCves[current.CveID]
|
||||
pre := map[string]bool{}
|
||||
for _, h := range preVinfo.AffectedPackages {
|
||||
pre[h.Name] = h.NotFixedYet
|
||||
}
|
||||
|
||||
cur := map[string]bool{}
|
||||
for _, h := range current.AffectedPackages {
|
||||
cur[h.Name] = h.NotFixedYet
|
||||
}
|
||||
|
||||
return !reflect.DeepEqual(pre, cur)
|
||||
}
|
||||
|
||||
func isCveInfoUpdated(cveID string, previous, current models.ScanResult) bool {
|
||||
cTypes := []models.CveContentType{
|
||||
models.Nvd,
|
||||
@@ -1,4 +1,4 @@
|
||||
package report
|
||||
package reporter
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -630,104 +630,3 @@ func TestMinusDiff(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsCveFixed(t *testing.T) {
|
||||
type In struct {
|
||||
v models.VulnInfo
|
||||
prev models.ScanResult
|
||||
}
|
||||
var tests = []struct {
|
||||
in In
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
in: In{
|
||||
v: models.VulnInfo{
|
||||
CveID: "CVE-2016-6662",
|
||||
AffectedPackages: models.PackageFixStatuses{
|
||||
{
|
||||
Name: "mysql-libs",
|
||||
NotFixedYet: false,
|
||||
},
|
||||
},
|
||||
CveContents: models.NewCveContents(
|
||||
models.CveContent{
|
||||
Type: models.Nvd,
|
||||
CveID: "CVE-2016-6662",
|
||||
LastModified: time.Time{},
|
||||
},
|
||||
),
|
||||
},
|
||||
prev: models.ScanResult{
|
||||
ScannedCves: models.VulnInfos{
|
||||
"CVE-2016-6662": {
|
||||
CveID: "CVE-2016-6662",
|
||||
AffectedPackages: models.PackageFixStatuses{
|
||||
{
|
||||
Name: "mysql-libs",
|
||||
NotFixedYet: true,
|
||||
},
|
||||
},
|
||||
CveContents: models.NewCveContents(
|
||||
models.CveContent{
|
||||
Type: models.Nvd,
|
||||
CveID: "CVE-2016-6662",
|
||||
LastModified: time.Time{},
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
in: In{
|
||||
v: models.VulnInfo{
|
||||
CveID: "CVE-2016-6662",
|
||||
AffectedPackages: models.PackageFixStatuses{
|
||||
{
|
||||
Name: "mysql-libs",
|
||||
NotFixedYet: true,
|
||||
},
|
||||
},
|
||||
CveContents: models.NewCveContents(
|
||||
models.CveContent{
|
||||
Type: models.Nvd,
|
||||
CveID: "CVE-2016-6662",
|
||||
LastModified: time.Time{},
|
||||
},
|
||||
),
|
||||
},
|
||||
prev: models.ScanResult{
|
||||
ScannedCves: models.VulnInfos{
|
||||
"CVE-2016-6662": {
|
||||
CveID: "CVE-2016-6662",
|
||||
AffectedPackages: models.PackageFixStatuses{
|
||||
{
|
||||
Name: "mysql-libs",
|
||||
NotFixedYet: true,
|
||||
},
|
||||
},
|
||||
CveContents: models.NewCveContents(
|
||||
models.CveContent{
|
||||
Type: models.Nvd,
|
||||
CveID: "CVE-2016-6662",
|
||||
LastModified: time.Time{},
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
actual := isCveFixed(tt.in.v, tt.in.prev)
|
||||
if actual != tt.expected {
|
||||
t.Errorf("[%d] actual: %t, expected: %t", i, actual, tt.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package report
|
||||
package reporter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -1,36 +0,0 @@
|
||||
package scan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
)
|
||||
|
||||
func isRunningKernel(pack models.Package, family string, kernel models.Kernel) (isKernel, running bool) {
|
||||
switch family {
|
||||
case config.SUSEEnterpriseServer:
|
||||
if pack.Name == "kernel-default" {
|
||||
// Remove the last period and later because uname don't show that.
|
||||
ss := strings.Split(pack.Release, ".")
|
||||
rel := strings.Join(ss[0:len(ss)-1], ".")
|
||||
ver := fmt.Sprintf("%s-%s-default", pack.Version, rel)
|
||||
return true, kernel.Release == ver
|
||||
}
|
||||
return false, false
|
||||
|
||||
case config.RedHat, config.Oracle, config.CentOS, config.Amazon:
|
||||
switch pack.Name {
|
||||
case "kernel", "kernel-devel":
|
||||
ver := fmt.Sprintf("%s-%s.%s", pack.Version, pack.Release, pack.Arch)
|
||||
return true, kernel.Release == ver
|
||||
}
|
||||
return false, false
|
||||
|
||||
default:
|
||||
util.Log.Warnf("Reboot required is not implemented yet: %s, %v", family, kernel)
|
||||
}
|
||||
return false, false
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
package scan
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"strings"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
"golang.org/x/xerrors"
|
||||
@@ -38,7 +39,7 @@ func detectAlpine(c config.ServerInfo) (bool, osTypeInterface) {
|
||||
}
|
||||
if r := exec(c, "cat /etc/alpine-release", noSudo); r.isSuccess() {
|
||||
os := newAlpine(c)
|
||||
os.setDistro(config.Alpine, strings.TrimSpace(r.Stdout))
|
||||
os.setDistro(constant.Alpine, strings.TrimSpace(r.Stdout))
|
||||
return true, os
|
||||
}
|
||||
return false, nil
|
||||
@@ -1,4 +1,4 @@
|
||||
package scan
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
@@ -1,4 +1,4 @@
|
||||
package scan
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"github.com/future-architect/vuls/config"
|
||||
@@ -1,4 +1,4 @@
|
||||
package scan
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/aquasecurity/fanal/analyzer"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -42,6 +43,24 @@ type base struct {
|
||||
warns []error
|
||||
}
|
||||
|
||||
// osPackages is included by base struct
|
||||
type osPackages struct {
|
||||
// installed packages
|
||||
Packages models.Packages
|
||||
|
||||
// installed source packages (Debian based only)
|
||||
SrcPackages models.SrcPackages
|
||||
|
||||
// enabled dnf modules or packages
|
||||
EnabledDnfModules []string
|
||||
|
||||
// unsecure packages
|
||||
VulnInfos models.VulnInfos
|
||||
|
||||
// kernel information
|
||||
Kernel models.Kernel
|
||||
}
|
||||
|
||||
func (l *base) exec(cmd string, sudo bool) execResult {
|
||||
return exec(l.ServerInfo, cmd, sudo, l.log)
|
||||
}
|
||||
@@ -86,7 +105,7 @@ func (l *base) runningKernel() (release, version string, err error) {
|
||||
release = strings.TrimSpace(r.Stdout)
|
||||
|
||||
switch l.Distro.Family {
|
||||
case config.Debian:
|
||||
case constant.Debian:
|
||||
r := l.exec("uname -a", noSudo)
|
||||
if !r.isSuccess() {
|
||||
return "", "", xerrors.Errorf("Failed to SSH: %s", r)
|
||||
@@ -338,13 +357,12 @@ func (l *base) detectDeepSecurity() (string, error) {
|
||||
return "", xerrors.Errorf("Failed to detect deepsecurity %s", l.ServerInfo.ServerName)
|
||||
}
|
||||
|
||||
func (l *base) detectIPSs() {
|
||||
func (l *base) detectIPS() {
|
||||
if !config.Conf.DetectIPS {
|
||||
return
|
||||
}
|
||||
|
||||
ips := map[config.IPS]string{}
|
||||
|
||||
ips := map[string]string{}
|
||||
fingerprint, err := l.detectDeepSecurity()
|
||||
if err != nil {
|
||||
return
|
||||
@@ -428,7 +446,7 @@ func (l *base) convertToModel() models.ScanResult {
|
||||
scannedVia := scannedViaRemote
|
||||
if isLocalExec(l.ServerInfo.Port, l.ServerInfo.Host) {
|
||||
scannedVia = scannedViaLocal
|
||||
} else if l.ServerInfo.Type == config.ServerTypePseudo {
|
||||
} else if l.ServerInfo.Type == constant.ServerTypePseudo {
|
||||
scannedVia = scannedViaPseudo
|
||||
}
|
||||
|
||||
@@ -568,7 +586,7 @@ func (l *base) scanLibraries() (err error) {
|
||||
|
||||
var bytes []byte
|
||||
switch l.Distro.Family {
|
||||
case config.ServerTypePseudo:
|
||||
case constant.ServerTypePseudo:
|
||||
bytes, err = ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to get target file: %s, filepath: %s", err, path)
|
||||
@@ -622,7 +640,7 @@ func (d *DummyFileInfo) IsDir() bool { return false }
|
||||
func (d *DummyFileInfo) Sys() interface{} { return nil }
|
||||
|
||||
func (l *base) scanWordPress() error {
|
||||
if l.ServerInfo.WordPress.IsZero() || l.ServerInfo.Type == config.ServerTypePseudo {
|
||||
if l.ServerInfo.WordPress.IsZero() || l.ServerInfo.Type == constant.ServerTypePseudo {
|
||||
return nil
|
||||
}
|
||||
l.log.Info("Scanning WordPress...")
|
||||
@@ -1,4 +1,4 @@
|
||||
package scan
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
@@ -1,4 +1,4 @@
|
||||
package scan
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"github.com/future-architect/vuls/config"
|
||||
@@ -1,4 +1,4 @@
|
||||
package scan
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/future-architect/vuls/cache"
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
version "github.com/knqyf263/go-deb-version"
|
||||
@@ -59,7 +60,7 @@ func detectDebian(c config.ServerInfo) (bool, osTypeInterface, error) {
|
||||
// e.g.
|
||||
// Raspbian GNU/Linux 7 \n \l
|
||||
result := strings.Fields(r.Stdout)
|
||||
if len(result) > 2 && result[0] == config.Raspbian {
|
||||
if len(result) > 2 && result[0] == constant.Raspbian {
|
||||
deb := newDebian(c)
|
||||
deb.setDistro(strings.ToLower(trim(result[0])), trim(result[2]))
|
||||
return true, deb, nil
|
||||
@@ -109,7 +110,7 @@ func detectDebian(c config.ServerInfo) (bool, osTypeInterface, error) {
|
||||
cmd := "cat /etc/debian_version"
|
||||
if r := exec(c, cmd, noSudo); r.isSuccess() {
|
||||
deb := newDebian(c)
|
||||
deb.setDistro(config.Debian, trim(r.Stdout))
|
||||
deb.setDistro(constant.Debian, trim(r.Stdout))
|
||||
return true, deb, nil
|
||||
}
|
||||
|
||||
@@ -186,7 +187,7 @@ func (o *debian) checkDeps() error {
|
||||
})
|
||||
}
|
||||
|
||||
if o.Distro.Family == config.Debian {
|
||||
if o.Distro.Family == constant.Debian {
|
||||
// https://askubuntu.com/a/742844
|
||||
if !o.ServerInfo.IsContainer() {
|
||||
deps = append(deps, dep{
|
||||
@@ -305,7 +306,7 @@ func (o *debian) scanPackages() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !o.getServerInfo().Mode.IsDeep() && o.Distro.Family == config.Raspbian {
|
||||
if !o.getServerInfo().Mode.IsDeep() && o.Distro.Family == constant.Raspbian {
|
||||
raspbianPacks := o.grepRaspbianPackages(updatable)
|
||||
unsecures, err := o.scanUnsecurePackages(raspbianPacks)
|
||||
if err != nil {
|
||||
@@ -502,7 +503,7 @@ func (o *debian) scanUnsecurePackages(updatable models.Packages) (models.VulnInf
|
||||
|
||||
// Make a directory for saving changelog to get changelog in Raspbian
|
||||
tmpClogPath := ""
|
||||
if o.Distro.Family == config.Raspbian {
|
||||
if o.Distro.Family == constant.Raspbian {
|
||||
tmpClogPath, err = o.makeTempChangelogDir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -516,7 +517,7 @@ func (o *debian) scanUnsecurePackages(updatable models.Packages) (models.VulnInf
|
||||
}
|
||||
|
||||
// Delete a directory for saving changelog to get changelog in Raspbian
|
||||
if o.Distro.Family == config.Raspbian {
|
||||
if o.Distro.Family == constant.Raspbian {
|
||||
err := o.deleteTempChangelogDir(tmpClogPath)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("Failed to delete directory to save changelog for Raspbian. err: %s", err)
|
||||
@@ -820,11 +821,11 @@ func (o *debian) fetchParseChangelog(pack models.Package, tmpClogPath string) ([
|
||||
cmd := ""
|
||||
|
||||
switch o.Distro.Family {
|
||||
case config.Ubuntu:
|
||||
case constant.Ubuntu:
|
||||
cmd = fmt.Sprintf(`PAGER=cat apt-get -q=2 changelog %s`, pack.Name)
|
||||
case config.Debian:
|
||||
case constant.Debian:
|
||||
cmd = fmt.Sprintf(`PAGER=cat aptitude -q=2 changelog %s`, pack.Name)
|
||||
case config.Raspbian:
|
||||
case constant.Raspbian:
|
||||
changelogPath, err := o.getChangelogPath(pack.Name, tmpClogPath)
|
||||
if err != nil {
|
||||
// Ignore this Error.
|
||||
@@ -936,10 +937,10 @@ func (o *debian) getCveIDsFromChangelog(
|
||||
|
||||
delim := []string{"+", "~", "build"}
|
||||
switch o.Distro.Family {
|
||||
case config.Ubuntu:
|
||||
delim = append(delim, config.Ubuntu)
|
||||
case config.Debian:
|
||||
case config.Raspbian:
|
||||
case constant.Ubuntu:
|
||||
delim = append(delim, constant.Ubuntu)
|
||||
case constant.Debian:
|
||||
case constant.Raspbian:
|
||||
}
|
||||
|
||||
for _, d := range delim {
|
||||
@@ -1014,7 +1015,7 @@ func (o *debian) parseChangelog(changelog, name, ver string, confidence models.C
|
||||
}
|
||||
|
||||
if !found {
|
||||
if o.Distro.Family == config.Raspbian {
|
||||
if o.Distro.Family == constant.Raspbian {
|
||||
pack := o.Packages[name]
|
||||
pack.Changelog = &models.Changelog{
|
||||
Contents: strings.Join(buf, "\n"),
|
||||
@@ -1,4 +1,4 @@
|
||||
package scan
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/future-architect/vuls/cache"
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/k0kubun/pp"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -851,7 +852,7 @@ vlc (3.0.11-0+deb10u1) buster-security; urgency=high
|
||||
}
|
||||
|
||||
o := newDebian(config.ServerInfo{})
|
||||
o.Distro = config.Distro{Family: config.Raspbian}
|
||||
o.Distro = config.Distro{Family: constant.Raspbian}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.packName, func(t *testing.T) {
|
||||
cveIDs, pack, _ := o.parseChangelog(tt.args.changelog, tt.args.name, tt.args.ver, models.ChangelogExactMatch)
|
||||
@@ -1,24 +1,16 @@
|
||||
package scan
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
ex "os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/cenkalti/backoff"
|
||||
conf "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/util"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
@@ -147,8 +139,6 @@ func exec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (resul
|
||||
|
||||
if isLocalExec(c.Port, c.Host) {
|
||||
result = localExec(c, cmd, sudo)
|
||||
} else if conf.Conf.SSHNative {
|
||||
result = sshExecNative(c, cmd, sudo)
|
||||
} else {
|
||||
result = sshExecExternal(c, cmd, sudo)
|
||||
}
|
||||
@@ -192,70 +182,10 @@ func localExec(c conf.ServerInfo, cmdstr string, sudo bool) (result execResult)
|
||||
return
|
||||
}
|
||||
|
||||
func sshExecNative(c conf.ServerInfo, cmd string, sudo bool) (result execResult) {
|
||||
result.Servername = c.ServerName
|
||||
result.Container = c.Container
|
||||
result.Host = c.Host
|
||||
result.Port = c.Port
|
||||
|
||||
var client *ssh.Client
|
||||
var err error
|
||||
if client, err = sshConnect(c); err != nil {
|
||||
result.Error = err
|
||||
result.ExitStatus = 999
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
var session *ssh.Session
|
||||
if session, err = client.NewSession(); err != nil {
|
||||
result.Error = xerrors.Errorf(
|
||||
"Failed to create a new session. servername: %s, err: %w",
|
||||
c.ServerName, err)
|
||||
result.ExitStatus = 999
|
||||
return
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
// http://blog.ralch.com/tutorial/golang-ssh-connection/
|
||||
modes := ssh.TerminalModes{
|
||||
ssh.ECHO: 0, // disable echoing
|
||||
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
|
||||
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
|
||||
}
|
||||
if err = session.RequestPty("xterm", 400, 1000, modes); err != nil {
|
||||
result.Error = xerrors.Errorf(
|
||||
"Failed to request for pseudo terminal. servername: %s, err: %w",
|
||||
c.ServerName, err)
|
||||
result.ExitStatus = 999
|
||||
return
|
||||
}
|
||||
|
||||
var stdoutBuf, stderrBuf bytes.Buffer
|
||||
session.Stdout = &stdoutBuf
|
||||
session.Stderr = &stderrBuf
|
||||
|
||||
cmd = decorateCmd(c, cmd, sudo)
|
||||
if err := session.Run(cmd); err != nil {
|
||||
if exitErr, ok := err.(*ssh.ExitError); ok {
|
||||
result.ExitStatus = exitErr.ExitStatus()
|
||||
} else {
|
||||
result.ExitStatus = 999
|
||||
}
|
||||
} else {
|
||||
result.ExitStatus = 0
|
||||
}
|
||||
|
||||
result.Stdout = stdoutBuf.String()
|
||||
result.Stderr = stderrBuf.String()
|
||||
result.Cmd = strings.Replace(cmd, "\n", "", -1)
|
||||
return
|
||||
}
|
||||
|
||||
func sshExecExternal(c conf.ServerInfo, cmd string, sudo bool) (result execResult) {
|
||||
sshBinaryPath, err := ex.LookPath("ssh")
|
||||
if err != nil {
|
||||
return sshExecNative(c, cmd, sudo)
|
||||
return execResult{Error: err}
|
||||
}
|
||||
|
||||
defaultSSHArgs := []string{"-tt"}
|
||||
@@ -383,115 +313,3 @@ func decorateCmd(c conf.ServerInfo, cmd string, sudo bool) string {
|
||||
// cmd = fmt.Sprintf("set -x; %s", cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func getAgentAuth() (auth ssh.AuthMethod, ok bool) {
|
||||
if sock := os.Getenv("SSH_AUTH_SOCK"); 0 < len(sock) {
|
||||
if agconn, err := net.Dial("unix", sock); err == nil {
|
||||
ag := agent.NewClient(agconn)
|
||||
auth = ssh.PublicKeysCallback(ag.Signers)
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func tryAgentConnect(c conf.ServerInfo) *ssh.Client {
|
||||
if auth, ok := getAgentAuth(); ok {
|
||||
config := &ssh.ClientConfig{
|
||||
User: c.User,
|
||||
Auth: []ssh.AuthMethod{auth},
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
client, _ := ssh.Dial("tcp", c.Host+":"+c.Port, config)
|
||||
return client
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sshConnect(c conf.ServerInfo) (client *ssh.Client, err error) {
|
||||
if client = tryAgentConnect(c); client != nil {
|
||||
return client, nil
|
||||
}
|
||||
|
||||
var auths = []ssh.AuthMethod{}
|
||||
if auths, err = addKeyAuth(auths, c.KeyPath, c.KeyPassword); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// http://blog.ralch.com/tutorial/golang-ssh-connection/
|
||||
config := &ssh.ClientConfig{
|
||||
User: c.User,
|
||||
Auth: auths,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
notifyFunc := func(e error, t time.Duration) {
|
||||
logger := getSSHLogger()
|
||||
logger.Debugf("Failed to Dial to %s, err: %s, Retrying in %s...",
|
||||
c.ServerName, e, t)
|
||||
}
|
||||
err = backoff.RetryNotify(func() error {
|
||||
if client, err = ssh.Dial("tcp", c.Host+":"+c.Port, config); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, backoff.NewExponentialBackOff(), notifyFunc)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// https://github.com/rapidloop/rtop/blob/ba5b35e964135d50e0babedf0bd69b2fcb5dbcb4/src/sshhelper.go#L100
|
||||
func addKeyAuth(auths []ssh.AuthMethod, keypath string, keypassword string) ([]ssh.AuthMethod, error) {
|
||||
if len(keypath) == 0 {
|
||||
return auths, nil
|
||||
}
|
||||
|
||||
// read the file
|
||||
pemBytes, err := ioutil.ReadFile(keypath)
|
||||
if err != nil {
|
||||
return auths, err
|
||||
}
|
||||
|
||||
// get first pem block
|
||||
block, _ := pem.Decode(pemBytes)
|
||||
if block == nil {
|
||||
return auths, xerrors.Errorf("no key found in %s", keypath)
|
||||
}
|
||||
|
||||
// handle plain and encrypted keyfiles
|
||||
if x509.IsEncryptedPEMBlock(block) {
|
||||
block.Bytes, err = x509.DecryptPEMBlock(block, []byte(keypassword))
|
||||
if err != nil {
|
||||
return auths, err
|
||||
}
|
||||
key, err := parsePemBlock(block)
|
||||
if err != nil {
|
||||
return auths, err
|
||||
}
|
||||
signer, err := ssh.NewSignerFromKey(key)
|
||||
if err != nil {
|
||||
return auths, err
|
||||
}
|
||||
return append(auths, ssh.PublicKeys(signer)), nil
|
||||
}
|
||||
|
||||
signer, err := ssh.ParsePrivateKey(pemBytes)
|
||||
if err != nil {
|
||||
return auths, err
|
||||
}
|
||||
return append(auths, ssh.PublicKeys(signer)), nil
|
||||
}
|
||||
|
||||
// ref golang.org/x/crypto/ssh/keys.go#ParseRawPrivateKey.
|
||||
func parsePemBlock(block *pem.Block) (interface{}, error) {
|
||||
switch block.Type {
|
||||
case "RSA PRIVATE KEY":
|
||||
return x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
case "EC PRIVATE KEY":
|
||||
return x509.ParseECPrivateKey(block.Bytes)
|
||||
case "DSA PRIVATE KEY":
|
||||
return ssh.ParseDSAPrivateKey(block.Bytes)
|
||||
default:
|
||||
return nil, xerrors.Errorf("Unsupported key type %q", block.Type)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package scan
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -1,10 +1,11 @@
|
||||
package scan
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
"golang.org/x/xerrors"
|
||||
@@ -33,14 +34,14 @@ func newBsd(c config.ServerInfo) *bsd {
|
||||
//https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/freebsd.rb
|
||||
func detectFreebsd(c config.ServerInfo) (bool, osTypeInterface) {
|
||||
// Prevent from adding `set -o pipefail` option
|
||||
c.Distro = config.Distro{Family: config.FreeBSD}
|
||||
c.Distro = config.Distro{Family: constant.FreeBSD}
|
||||
|
||||
if r := exec(c, "uname", noSudo); r.isSuccess() {
|
||||
if strings.Contains(strings.ToLower(r.Stdout), config.FreeBSD) == true {
|
||||
if strings.Contains(strings.ToLower(r.Stdout), constant.FreeBSD) == true {
|
||||
if b := exec(c, "freebsd-version", noSudo); b.isSuccess() {
|
||||
bsd := newBsd(c)
|
||||
rel := strings.TrimSpace(b.Stdout)
|
||||
bsd.setDistro(config.FreeBSD, rel)
|
||||
bsd.setDistro(constant.FreeBSD, rel)
|
||||
return true, bsd
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package scan
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
@@ -1,4 +1,4 @@
|
||||
package scan
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"github.com/aquasecurity/fanal/types"
|
||||
@@ -1,4 +1,4 @@
|
||||
package scan
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"github.com/future-architect/vuls/config"
|
||||
@@ -1,7 +1,8 @@
|
||||
package scan
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
)
|
||||
@@ -12,9 +13,9 @@ type pseudo struct {
|
||||
}
|
||||
|
||||
func detectPseudo(c config.ServerInfo) (itsMe bool, pseudo osTypeInterface, err error) {
|
||||
if c.Type == config.ServerTypePseudo {
|
||||
if c.Type == constant.ServerTypePseudo {
|
||||
p := newPseudo(c)
|
||||
p.setDistro(config.ServerTypePseudo, "")
|
||||
p.setDistro(constant.ServerTypePseudo, "")
|
||||
return true, p, nil
|
||||
}
|
||||
return false, nil, nil
|
||||
@@ -1,4 +1,4 @@
|
||||
package scan
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
"golang.org/x/xerrors"
|
||||
@@ -34,7 +35,7 @@ func detectRedhat(c config.ServerInfo) (bool, osTypeInterface) {
|
||||
|
||||
ora := newOracle(c)
|
||||
release := result[2]
|
||||
ora.setDistro(config.Oracle, release)
|
||||
ora.setDistro(constant.Oracle, release)
|
||||
return true, ora
|
||||
}
|
||||
}
|
||||
@@ -54,7 +55,7 @@ func detectRedhat(c config.ServerInfo) (bool, osTypeInterface) {
|
||||
switch strings.ToLower(result[1]) {
|
||||
case "centos", "centos linux", "centos stream":
|
||||
cent := newCentOS(c)
|
||||
cent.setDistro(config.CentOS, release)
|
||||
cent.setDistro(constant.CentOS, release)
|
||||
return true, cent
|
||||
default:
|
||||
util.Log.Warnf("Failed to parse CentOS: %s", r)
|
||||
@@ -79,19 +80,19 @@ func detectRedhat(c config.ServerInfo) (bool, osTypeInterface) {
|
||||
switch strings.ToLower(result[1]) {
|
||||
case "centos", "centos linux":
|
||||
cent := newCentOS(c)
|
||||
cent.setDistro(config.CentOS, release)
|
||||
cent.setDistro(constant.CentOS, release)
|
||||
return true, cent
|
||||
default:
|
||||
// RHEL
|
||||
rhel := newRHEL(c)
|
||||
rhel.setDistro(config.RedHat, release)
|
||||
rhel.setDistro(constant.RedHat, release)
|
||||
return true, rhel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if r := exec(c, "ls /etc/system-release", noSudo); r.isSuccess() {
|
||||
family := config.Amazon
|
||||
family := constant.Amazon
|
||||
release := "unknown"
|
||||
if r := exec(c, "cat /etc/system-release", noSudo); r.isSuccess() {
|
||||
if strings.HasPrefix(r.Stdout, "Amazon Linux release 2") {
|
||||
@@ -218,7 +219,7 @@ func (o *redhatBase) scanPackages() (err error) {
|
||||
|
||||
if o.getServerInfo().Mode.IsOffline() {
|
||||
return nil
|
||||
} else if o.Distro.Family == config.RedHat {
|
||||
} else if o.Distro.Family == constant.RedHat {
|
||||
if o.getServerInfo().Mode.IsFast() {
|
||||
return nil
|
||||
}
|
||||
@@ -426,12 +427,12 @@ func (o *redhatBase) parseUpdatablePacksLine(line string) (models.Package, error
|
||||
|
||||
func (o *redhatBase) isExecYumPS() bool {
|
||||
switch o.Distro.Family {
|
||||
case config.Oracle,
|
||||
config.OpenSUSE,
|
||||
config.OpenSUSELeap,
|
||||
config.SUSEEnterpriseServer,
|
||||
config.SUSEEnterpriseDesktop,
|
||||
config.SUSEOpenstackCloud:
|
||||
case constant.Oracle,
|
||||
constant.OpenSUSE,
|
||||
constant.OpenSUSELeap,
|
||||
constant.SUSEEnterpriseServer,
|
||||
constant.SUSEEnterpriseDesktop,
|
||||
constant.SUSEOpenstackCloud:
|
||||
return false
|
||||
}
|
||||
return !o.getServerInfo().Mode.IsFast()
|
||||
@@ -439,15 +440,15 @@ func (o *redhatBase) isExecYumPS() bool {
|
||||
|
||||
func (o *redhatBase) isExecNeedsRestarting() bool {
|
||||
switch o.Distro.Family {
|
||||
case config.OpenSUSE,
|
||||
config.OpenSUSELeap,
|
||||
config.SUSEEnterpriseServer,
|
||||
config.SUSEEnterpriseDesktop,
|
||||
config.SUSEOpenstackCloud:
|
||||
case constant.OpenSUSE,
|
||||
constant.OpenSUSELeap,
|
||||
constant.SUSEEnterpriseServer,
|
||||
constant.SUSEEnterpriseDesktop,
|
||||
constant.SUSEOpenstackCloud:
|
||||
// TODO zypper ps
|
||||
// https://github.com/future-architect/vuls/issues/696
|
||||
return false
|
||||
case config.RedHat, config.CentOS, config.Oracle:
|
||||
case constant.RedHat, constant.CentOS, constant.Oracle:
|
||||
majorVersion, err := o.Distro.MajorVersion()
|
||||
if err != nil || majorVersion < 6 {
|
||||
o.log.Errorf("Not implemented yet: %s, err: %s", o.Distro, err)
|
||||
@@ -594,7 +595,7 @@ func (o *redhatBase) rpmQa() string {
|
||||
const old = `rpm -qa --queryformat "%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n"`
|
||||
const new = `rpm -qa --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n"`
|
||||
switch o.Distro.Family {
|
||||
case config.SUSEEnterpriseServer:
|
||||
case constant.SUSEEnterpriseServer:
|
||||
if v, _ := o.Distro.MajorVersion(); v < 12 {
|
||||
return old
|
||||
}
|
||||
@@ -611,7 +612,7 @@ func (o *redhatBase) rpmQf() string {
|
||||
const old = `rpm -qf --queryformat "%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n" `
|
||||
const new = `rpm -qf --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n" `
|
||||
switch o.Distro.Family {
|
||||
case config.SUSEEnterpriseServer:
|
||||
case constant.SUSEEnterpriseServer:
|
||||
if v, _ := o.Distro.MajorVersion(); v < 12 {
|
||||
return old
|
||||
}
|
||||
@@ -626,7 +627,7 @@ func (o *redhatBase) rpmQf() string {
|
||||
|
||||
func (o *redhatBase) detectEnabledDnfModules() ([]string, error) {
|
||||
switch o.Distro.Family {
|
||||
case config.RedHat, config.CentOS:
|
||||
case constant.RedHat, constant.CentOS:
|
||||
//TODO OracleLinux
|
||||
default:
|
||||
return nil, nil
|
||||
@@ -1,10 +1,11 @@
|
||||
package scan
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/k0kubun/pp"
|
||||
)
|
||||
@@ -16,7 +17,7 @@ import (
|
||||
|
||||
func TestParseInstalledPackagesLinesRedhat(t *testing.T) {
|
||||
r := newRHEL(config.ServerInfo{})
|
||||
r.Distro = config.Distro{Family: config.RedHat}
|
||||
r.Distro = config.Distro{Family: constant.RedHat}
|
||||
|
||||
var packagetests = []struct {
|
||||
in string
|
||||
@@ -1,4 +1,4 @@
|
||||
package scan
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"github.com/future-architect/vuls/config"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,17 @@
|
||||
package scan
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/models"
|
||||
)
|
||||
|
||||
func TestViaHTTP(t *testing.T) {
|
||||
r := newRHEL(config.ServerInfo{})
|
||||
r.Distro = config.Distro{Family: config.RedHat}
|
||||
r.Distro = config.Distro{Family: constant.RedHat}
|
||||
|
||||
var tests = []struct {
|
||||
header map[string]string
|
||||
@@ -102,7 +103,7 @@ func TestViaHTTP(t *testing.T) {
|
||||
header.Set(k, v)
|
||||
}
|
||||
|
||||
result, err := ViaHTTP(header, tt.body)
|
||||
result, err := ViaHTTP(header, tt.body, false)
|
||||
if err != tt.wantErr {
|
||||
t.Errorf("error: expected %s, actual: %s", tt.wantErr, err)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package scan
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
"golang.org/x/xerrors"
|
||||
@@ -53,7 +54,7 @@ func detectSUSE(c config.ServerInfo) (bool, osTypeInterface) {
|
||||
if len(result) == 2 {
|
||||
//TODO check opensuse or opensuse.leap
|
||||
s := newSUSE(c)
|
||||
s.setDistro(config.OpenSUSE, result[1])
|
||||
s.setDistro(constant.OpenSUSE, result[1])
|
||||
return true, s
|
||||
}
|
||||
|
||||
@@ -65,7 +66,7 @@ func detectSUSE(c config.ServerInfo) (bool, osTypeInterface) {
|
||||
result = re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
|
||||
if len(result) == 2 {
|
||||
s := newSUSE(c)
|
||||
s.setDistro(config.SUSEEnterpriseServer,
|
||||
s.setDistro(constant.SUSEEnterpriseServer,
|
||||
fmt.Sprintf("%s.%s", version, result[1]))
|
||||
return true, s
|
||||
}
|
||||
@@ -82,11 +83,11 @@ func detectSUSE(c config.ServerInfo) (bool, osTypeInterface) {
|
||||
func (o *suse) parseOSRelease(content string) (name string, ver string) {
|
||||
if strings.Contains(content, "ID=opensuse") {
|
||||
//TODO check opensuse or opensuse.leap
|
||||
name = config.OpenSUSE
|
||||
name = constant.OpenSUSE
|
||||
} else if strings.Contains(content, `NAME="SLES"`) {
|
||||
name = config.SUSEEnterpriseServer
|
||||
name = constant.SUSEEnterpriseServer
|
||||
} else if strings.Contains(content, `NAME="SLES_SAP"`) {
|
||||
name = config.SUSEEnterpriseServer
|
||||
name = constant.SUSEEnterpriseServer
|
||||
} else {
|
||||
util.Log.Warnf("Failed to parse SUSE edition: %s", content)
|
||||
return "unknown", "unknown"
|
||||
@@ -1,10 +1,11 @@
|
||||
package scan
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/k0kubun/pp"
|
||||
)
|
||||
@@ -107,7 +108,7 @@ func TestParseOSRelease(t *testing.T) {
|
||||
in: `NAME="openSUSE Leap"
|
||||
ID=opensuse
|
||||
VERSION_ID="42.3.4"`,
|
||||
name: config.OpenSUSE,
|
||||
name: constant.OpenSUSE,
|
||||
ver: "42.3.4",
|
||||
},
|
||||
{
|
||||
@@ -115,7 +116,7 @@ VERSION_ID="42.3.4"`,
|
||||
VERSION="12-SP1"
|
||||
VERSION_ID="12.1"
|
||||
ID="sles"`,
|
||||
name: config.SUSEEnterpriseServer,
|
||||
name: constant.SUSEEnterpriseServer,
|
||||
ver: "12.1",
|
||||
},
|
||||
{
|
||||
@@ -123,7 +124,7 @@ ID="sles"`,
|
||||
VERSION="12-SP1"
|
||||
VERSION_ID="12.1.0.1"
|
||||
ID="sles"`,
|
||||
name: config.SUSEEnterpriseServer,
|
||||
name: constant.SUSEEnterpriseServer,
|
||||
ver: "12.1.0.1",
|
||||
},
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package scan
|
||||
package scanner
|
||||
|
||||
import "github.com/future-architect/vuls/models"
|
||||
|
||||
96
scanner/utils.go
Normal file
96
scanner/utils.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/reporter"
|
||||
"github.com/future-architect/vuls/util"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
func isRunningKernel(pack models.Package, family string, kernel models.Kernel) (isKernel, running bool) {
|
||||
switch family {
|
||||
case constant.SUSEEnterpriseServer:
|
||||
if pack.Name == "kernel-default" {
|
||||
// Remove the last period and later because uname don't show that.
|
||||
ss := strings.Split(pack.Release, ".")
|
||||
rel := strings.Join(ss[0:len(ss)-1], ".")
|
||||
ver := fmt.Sprintf("%s-%s-default", pack.Version, rel)
|
||||
return true, kernel.Release == ver
|
||||
}
|
||||
return false, false
|
||||
|
||||
case constant.RedHat, constant.Oracle, constant.CentOS, constant.Amazon:
|
||||
switch pack.Name {
|
||||
case "kernel", "kernel-devel":
|
||||
ver := fmt.Sprintf("%s-%s.%s", pack.Version, pack.Release, pack.Arch)
|
||||
return true, kernel.Release == ver
|
||||
}
|
||||
return false, false
|
||||
|
||||
default:
|
||||
util.Log.Warnf("Reboot required is not implemented yet: %s, %v", family, kernel)
|
||||
}
|
||||
return false, false
|
||||
}
|
||||
|
||||
// EnsureResultDir ensures the directory for scan results
|
||||
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 "", xerrors.Errorf("Failed to create dir: %w", err)
|
||||
}
|
||||
|
||||
symlinkPath := filepath.Join(resultsDir, "current")
|
||||
if _, err := os.Lstat(symlinkPath); err == nil {
|
||||
if err := os.Remove(symlinkPath); err != nil {
|
||||
return "", xerrors.Errorf(
|
||||
"Failed to remove symlink. path: %s, err: %w", symlinkPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.Symlink(jsonDir, symlinkPath); err != nil {
|
||||
return "", xerrors.Errorf(
|
||||
"Failed to create symlink: path: %s, err: %w", symlinkPath, err)
|
||||
}
|
||||
return jsonDir, nil
|
||||
}
|
||||
|
||||
func writeScanResults(jsonDir string, results models.ScanResults) error {
|
||||
ws := []reporter.ResultWriter{reporter.LocalFileWriter{
|
||||
CurrentDir: jsonDir,
|
||||
FormatJSON: true,
|
||||
}}
|
||||
for _, w := range ws {
|
||||
if err := w.Write(results...); err != nil {
|
||||
return xerrors.Errorf("Failed to write summary: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
reporter.StdoutWriter{}.WriteScanSummary(results...)
|
||||
|
||||
errServerNames := []string{}
|
||||
for _, r := range results {
|
||||
if 0 < len(r.Errors) {
|
||||
errServerNames = append(errServerNames, r.ServerName)
|
||||
}
|
||||
}
|
||||
if 0 < len(errServerNames) {
|
||||
return fmt.Errorf("An error occurred on %s", errServerNames)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,15 +1,16 @@
|
||||
package scan
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/models"
|
||||
)
|
||||
|
||||
func TestIsRunningKernelSUSE(t *testing.T) {
|
||||
r := newSUSE(config.ServerInfo{})
|
||||
r.Distro = config.Distro{Family: config.SUSEEnterpriseServer}
|
||||
r.Distro = config.Distro{Family: constant.SUSEEnterpriseServer}
|
||||
|
||||
kernel := models.Kernel{
|
||||
Release: "4.4.74-92.35-default",
|
||||
@@ -29,7 +30,7 @@ func TestIsRunningKernelSUSE(t *testing.T) {
|
||||
Release: "92.35.1",
|
||||
Arch: "x86_64",
|
||||
},
|
||||
family: config.SUSEEnterpriseServer,
|
||||
family: constant.SUSEEnterpriseServer,
|
||||
kernel: kernel,
|
||||
expected: true,
|
||||
},
|
||||
@@ -40,7 +41,7 @@ func TestIsRunningKernelSUSE(t *testing.T) {
|
||||
Release: "92.20.2",
|
||||
Arch: "x86_64",
|
||||
},
|
||||
family: config.SUSEEnterpriseServer,
|
||||
family: constant.SUSEEnterpriseServer,
|
||||
kernel: kernel,
|
||||
expected: false,
|
||||
},
|
||||
@@ -56,7 +57,7 @@ func TestIsRunningKernelSUSE(t *testing.T) {
|
||||
|
||||
func TestIsRunningKernelRedHatLikeLinux(t *testing.T) {
|
||||
r := newAmazon(config.ServerInfo{})
|
||||
r.Distro = config.Distro{Family: config.Amazon}
|
||||
r.Distro = config.Distro{Family: constant.Amazon}
|
||||
|
||||
kernel := models.Kernel{
|
||||
Release: "4.9.43-17.38.amzn1.x86_64",
|
||||
@@ -76,7 +77,7 @@ func TestIsRunningKernelRedHatLikeLinux(t *testing.T) {
|
||||
Release: "17.38.amzn1",
|
||||
Arch: "x86_64",
|
||||
},
|
||||
family: config.Amazon,
|
||||
family: constant.Amazon,
|
||||
kernel: kernel,
|
||||
expected: true,
|
||||
},
|
||||
@@ -87,7 +88,7 @@ func TestIsRunningKernelRedHatLikeLinux(t *testing.T) {
|
||||
Release: "16.35.amzn1",
|
||||
Arch: "x86_64",
|
||||
},
|
||||
family: config.Amazon,
|
||||
family: constant.Amazon,
|
||||
kernel: kernel,
|
||||
expected: false,
|
||||
},
|
||||
@@ -1 +0,0 @@
|
||||
package server
|
||||
@@ -11,16 +11,17 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
c "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/detector"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/report"
|
||||
"github.com/future-architect/vuls/scan"
|
||||
"github.com/future-architect/vuls/reporter"
|
||||
"github.com/future-architect/vuls/scanner"
|
||||
"github.com/future-architect/vuls/util"
|
||||
)
|
||||
|
||||
// VulsHandler is used for vuls server mode
|
||||
type VulsHandler struct {
|
||||
DBclient report.DBClient
|
||||
DBclient detector.DBClient
|
||||
ToLocalFile bool
|
||||
}
|
||||
|
||||
// ServeHTTP is http handler
|
||||
@@ -48,7 +49,7 @@ func (h VulsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if result, err = scan.ViaHTTP(req.Header, buf.String()); err != nil {
|
||||
if result, err = scanner.ViaHTTP(req.Header, buf.String(), h.ToLocalFile); err != nil {
|
||||
util.Log.Error(err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
@@ -59,13 +60,13 @@ func (h VulsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := report.DetectPkgCves(h.DBclient, &result); err != nil {
|
||||
if err := detector.DetectPkgCves(h.DBclient, &result); err != nil {
|
||||
util.Log.Errorf("Failed to detect Pkg CVE: %+v", err)
|
||||
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
if err := report.FillCveInfo(h.DBclient, &result); err != nil {
|
||||
if err := detector.FillCveInfo(h.DBclient, &result); err != nil {
|
||||
util.Log.Errorf("Failed to fill CVE detailed info: %+v", err)
|
||||
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||
return
|
||||
@@ -78,24 +79,26 @@ func (h VulsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
|
||||
// report
|
||||
reports := []report.ResultWriter{
|
||||
report.HTTPResponseWriter{Writer: w},
|
||||
reports := []reporter.ResultWriter{
|
||||
reporter.HTTPResponseWriter{Writer: w},
|
||||
}
|
||||
if c.Conf.ToLocalFile {
|
||||
if h.ToLocalFile {
|
||||
scannedAt := result.ScannedAt
|
||||
if scannedAt.IsZero() {
|
||||
scannedAt = time.Now().Truncate(1 * time.Hour)
|
||||
result.ScannedAt = scannedAt
|
||||
}
|
||||
dir, err := scan.EnsureResultDir(scannedAt)
|
||||
dir, err := scanner.EnsureResultDir(scannedAt)
|
||||
if err != nil {
|
||||
util.Log.Errorf("Failed to ensure the result directory: %+v", err)
|
||||
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
reports = append(reports, report.LocalFileWriter{
|
||||
// sever subcmd doesn't have diff option
|
||||
reports = append(reports, reporter.LocalFileWriter{
|
||||
CurrentDir: dir,
|
||||
FormatJSON: true,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/google/subcommands"
|
||||
|
||||
c "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/scan"
|
||||
"github.com/future-architect/vuls/scanner"
|
||||
"github.com/future-architect/vuls/util"
|
||||
)
|
||||
|
||||
@@ -64,9 +64,6 @@ func (p *ConfigtestCmd) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&c.Conf.HTTPProxy, "http-proxy", "",
|
||||
"http://proxy-url:port (default: empty)")
|
||||
|
||||
f.BoolVar(&c.Conf.SSHNative, "ssh-native-insecure", false,
|
||||
"Use Native Go implementation of SSH. Default: Use the external command")
|
||||
|
||||
f.BoolVar(&c.Conf.Vvv, "vvv", false, "ssh -vvv")
|
||||
}
|
||||
|
||||
@@ -105,12 +102,12 @@ func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interfa
|
||||
servernames = f.Args()
|
||||
}
|
||||
|
||||
target := make(map[string]c.ServerInfo)
|
||||
targets := make(map[string]c.ServerInfo)
|
||||
for _, arg := range servernames {
|
||||
found := false
|
||||
for servername, info := range c.Conf.Servers {
|
||||
if servername == arg {
|
||||
target[servername] = info
|
||||
targets[servername] = info
|
||||
found = true
|
||||
break
|
||||
}
|
||||
@@ -121,7 +118,7 @@ func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interfa
|
||||
}
|
||||
}
|
||||
if 0 < len(servernames) {
|
||||
c.Conf.Servers = target
|
||||
c.Conf.Servers = targets
|
||||
}
|
||||
|
||||
util.Log.Info("Validating config...")
|
||||
@@ -129,28 +126,15 @@ func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interfa
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
|
||||
util.Log.Info("Detecting Server/Container OS... ")
|
||||
if err := scan.InitServers(p.timeoutSec); err != nil {
|
||||
util.Log.Errorf("Failed to init servers. err: %+v", err)
|
||||
s := scanner.Scanner{
|
||||
TimeoutSec: p.timeoutSec,
|
||||
Targets: targets,
|
||||
}
|
||||
|
||||
if err := s.Configtest(); err != nil {
|
||||
util.Log.Errorf("Failed to configtest: %+v", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
util.Log.Info("Checking Scan Modes...")
|
||||
if err := scan.CheckScanModes(); err != nil {
|
||||
util.Log.Errorf("Fix config.toml. err: %+v", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
util.Log.Info("Checking dependencies...")
|
||||
scan.CheckDependencies(p.timeoutSec)
|
||||
|
||||
util.Log.Info("Checking sudo settings...")
|
||||
scan.CheckIfSudoNoPasswd(p.timeoutSec)
|
||||
|
||||
util.Log.Info("It can be scanned with fast scan mode even if warn or err messages are displayed due to lack of dependent packages or sudo settings in fast-root or deep scan mode")
|
||||
|
||||
if scan.PrintSSHableServerNames() {
|
||||
return subcommands.ExitSuccess
|
||||
}
|
||||
return subcommands.ExitFailure
|
||||
return subcommands.ExitSuccess
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"strings"
|
||||
|
||||
c "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/report"
|
||||
"github.com/future-architect/vuls/reporter"
|
||||
"github.com/google/subcommands"
|
||||
)
|
||||
|
||||
@@ -45,7 +45,7 @@ func (p *HistoryCmd) SetFlags(f *flag.FlagSet) {
|
||||
// Execute execute
|
||||
func (p *HistoryCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
|
||||
dirs, err := report.ListValidJSONDirs()
|
||||
dirs, err := reporter.ListValidJSONDirs()
|
||||
if err != nil {
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
@@ -11,8 +11,9 @@ import (
|
||||
"github.com/aquasecurity/trivy/pkg/utils"
|
||||
"github.com/future-architect/vuls/config"
|
||||
c "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/detector"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/report"
|
||||
"github.com/future-architect/vuls/reporter"
|
||||
"github.com/future-architect/vuls/util"
|
||||
"github.com/google/subcommands"
|
||||
"github.com/k0kubun/pp"
|
||||
@@ -21,7 +22,26 @@ import (
|
||||
// ReportCmd is subcommand for reporting
|
||||
type ReportCmd struct {
|
||||
configPath string
|
||||
httpConf c.HTTPConf
|
||||
|
||||
formatJSON bool
|
||||
formatOneEMail bool
|
||||
formatCsv bool
|
||||
formatFullText bool
|
||||
formatOneLineText bool
|
||||
formatList bool
|
||||
gzip bool
|
||||
|
||||
toSlack bool
|
||||
toChatWork bool
|
||||
toTelegram bool
|
||||
toEmail bool
|
||||
toSyslog bool
|
||||
toLocalFile bool
|
||||
toS3 bool
|
||||
toAzureBlob bool
|
||||
toHTTP bool
|
||||
|
||||
toHTTPURL string
|
||||
}
|
||||
|
||||
// Name return subcommand name
|
||||
@@ -119,32 +139,31 @@ func (p *ReportCmd) SetFlags(f *flag.FlagSet) {
|
||||
&c.Conf.HTTPProxy, "http-proxy", "",
|
||||
"http://proxy-url:port (default: empty)")
|
||||
|
||||
f.BoolVar(&c.Conf.FormatJSON, "format-json", false, "JSON format")
|
||||
f.BoolVar(&c.Conf.FormatCsvList, "format-csv", false, "CSV format")
|
||||
f.BoolVar(&c.Conf.FormatOneEMail, "format-one-email", false,
|
||||
f.BoolVar(&p.formatJSON, "format-json", false, "JSON format")
|
||||
f.BoolVar(&p.formatCsv, "format-csv", false, "CSV format")
|
||||
f.BoolVar(&p.formatOneEMail, "format-one-email", false,
|
||||
"Send all the host report via only one EMail (Specify with -to-email)")
|
||||
f.BoolVar(&c.Conf.FormatOneLineText, "format-one-line-text", false,
|
||||
f.BoolVar(&p.formatOneLineText, "format-one-line-text", false,
|
||||
"One line summary in plain text")
|
||||
f.BoolVar(&c.Conf.FormatList, "format-list", false, "Display as list format")
|
||||
f.BoolVar(&c.Conf.FormatFullText, "format-full-text", false,
|
||||
f.BoolVar(&p.formatList, "format-list", false, "Display as list format")
|
||||
f.BoolVar(&p.formatFullText, "format-full-text", false,
|
||||
"Detail report in plain text")
|
||||
|
||||
f.BoolVar(&c.Conf.ToSlack, "to-slack", false, "Send report via Slack")
|
||||
f.BoolVar(&c.Conf.ToChatWork, "to-chatwork", false, "Send report via chatwork")
|
||||
f.BoolVar(&c.Conf.ToTelegram, "to-telegram", false, "Send report via Telegram")
|
||||
f.BoolVar(&c.Conf.ToEmail, "to-email", false, "Send report via Email")
|
||||
f.BoolVar(&c.Conf.ToSyslog, "to-syslog", false, "Send report via Syslog")
|
||||
f.BoolVar(&c.Conf.ToLocalFile, "to-localfile", false, "Write report to localfile")
|
||||
f.BoolVar(&c.Conf.ToS3, "to-s3", false,
|
||||
"Write report to S3 (bucket/yyyyMMdd_HHmm/servername.json/txt)")
|
||||
f.BoolVar(&c.Conf.ToHTTP, "to-http", false, "Send report via HTTP POST")
|
||||
f.BoolVar(&c.Conf.ToAzureBlob, "to-azure-blob", false,
|
||||
f.BoolVar(&p.toSlack, "to-slack", false, "Send report via Slack")
|
||||
f.BoolVar(&p.toChatWork, "to-chatwork", false, "Send report via chatwork")
|
||||
f.BoolVar(&p.toTelegram, "to-telegram", false, "Send report via Telegram")
|
||||
f.BoolVar(&p.toEmail, "to-email", false, "Send report via Email")
|
||||
f.BoolVar(&p.toSyslog, "to-syslog", false, "Send report via Syslog")
|
||||
f.BoolVar(&p.toLocalFile, "to-localfile", false, "Write report to localfile")
|
||||
f.BoolVar(&p.toS3, "to-s3", false, "Write report to S3 (bucket/yyyyMMdd_HHmm/servername.json/txt)")
|
||||
f.BoolVar(&p.toHTTP, "to-http", false, "Send report via HTTP POST")
|
||||
f.BoolVar(&p.toAzureBlob, "to-azure-blob", false,
|
||||
"Write report to Azure Storage blob (container/yyyyMMdd_HHmm/servername.json/txt)")
|
||||
|
||||
f.BoolVar(&c.Conf.GZIP, "gzip", false, "gzip compression")
|
||||
f.BoolVar(&p.gzip, "gzip", false, "gzip compression")
|
||||
f.BoolVar(&c.Conf.Pipe, "pipe", false, "Use args passed via PIPE")
|
||||
|
||||
f.StringVar(&p.httpConf.URL, "http", "", "-to-http http://vuls-report")
|
||||
f.StringVar(&p.toHTTPURL, "http", "", "-to-http http://vuls-report")
|
||||
|
||||
f.StringVar(&c.Conf.TrivyCacheDBDir, "trivy-cachedb-dir",
|
||||
utils.DefaultCacheDir(), "/path/to/dir")
|
||||
@@ -153,23 +172,33 @@ func (p *ReportCmd) SetFlags(f *flag.FlagSet) {
|
||||
// Execute execute
|
||||
func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
util.Log = util.NewCustomLogger(c.ServerInfo{})
|
||||
|
||||
if err := c.Load(p.configPath, ""); err != nil {
|
||||
util.Log.Errorf("Error loading %s, %+v", p.configPath, err)
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
c.Conf.HTTP.Init(p.httpConf)
|
||||
c.Conf.Slack.Enabled = p.toSlack
|
||||
c.Conf.ChatWork.Enabled = p.toChatWork
|
||||
c.Conf.Telegram.Enabled = p.toTelegram
|
||||
c.Conf.EMail.Enabled = p.toEmail
|
||||
c.Conf.Syslog.Enabled = p.toSyslog
|
||||
c.Conf.AWS.Enabled = p.toS3
|
||||
c.Conf.Azure.Enabled = p.toAzureBlob
|
||||
c.Conf.HTTP.Enabled = p.toHTTP
|
||||
|
||||
//TODO refactor
|
||||
c.Conf.HTTP.Init(p.toHTTPURL)
|
||||
|
||||
if c.Conf.Diff {
|
||||
c.Conf.DiffPlus = true
|
||||
c.Conf.DiffMinus = true
|
||||
c.Conf.DiffPlus, c.Conf.DiffMinus = true, true
|
||||
}
|
||||
|
||||
var dir string
|
||||
var err error
|
||||
if c.Conf.DiffPlus || c.Conf.DiffMinus {
|
||||
dir, err = report.JSONDir([]string{})
|
||||
dir, err = reporter.JSONDir([]string{})
|
||||
} else {
|
||||
dir, err = report.JSONDir(f.Args())
|
||||
dir, err = reporter.JSONDir(f.Args())
|
||||
}
|
||||
if err != nil {
|
||||
util.Log.Errorf("Failed to read from JSON: %+v", err)
|
||||
@@ -181,13 +210,13 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
|
||||
if !(c.Conf.FormatJSON || c.Conf.FormatOneLineText ||
|
||||
c.Conf.FormatList || c.Conf.FormatFullText || c.Conf.FormatCsvList) {
|
||||
c.Conf.FormatList = true
|
||||
if !(p.formatJSON || p.formatOneLineText ||
|
||||
p.formatList || p.formatFullText || p.formatCsv) {
|
||||
p.formatList = true
|
||||
}
|
||||
|
||||
var loaded models.ScanResults
|
||||
if loaded, err = report.LoadScanResults(dir); err != nil {
|
||||
if loaded, err = reporter.LoadScanResults(dir); err != nil {
|
||||
util.Log.Error(err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
@@ -211,8 +240,7 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}
|
||||
|
||||
for _, r := range res {
|
||||
util.Log.Debugf("%s: %s",
|
||||
r.ServerInfo(),
|
||||
pp.Sprintf("%s", c.Conf.Servers[r.ServerName]))
|
||||
r.ServerInfo(), pp.Sprintf("%s", c.Conf.Servers[r.ServerName]))
|
||||
}
|
||||
|
||||
util.Log.Info("Validating db config...")
|
||||
@@ -233,7 +261,8 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}
|
||||
}
|
||||
}
|
||||
|
||||
dbclient, locked, err := report.NewDBClient(report.DBClientConf{
|
||||
// TODO move into fillcveInfos
|
||||
dbclient, locked, err := detector.NewDBClient(detector.DBClientConf{
|
||||
CveDictCnf: c.Conf.CveDict,
|
||||
OvalDictCnf: c.Conf.OvalDict,
|
||||
GostCnf: c.Conf.Gost,
|
||||
@@ -251,74 +280,107 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}
|
||||
}
|
||||
defer dbclient.CloseDB()
|
||||
|
||||
if res, err = report.FillCveInfos(*dbclient, res, dir); err != nil {
|
||||
// TODO pass conf by arg
|
||||
if res, err = detector.Detect(*dbclient, res, dir); err != nil {
|
||||
util.Log.Errorf("%+v", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
// report
|
||||
reports := []report.ResultWriter{
|
||||
report.StdoutWriter{},
|
||||
reports := []reporter.ResultWriter{
|
||||
reporter.StdoutWriter{
|
||||
FormatCsv: p.formatCsv,
|
||||
FormatFullText: p.formatFullText,
|
||||
FormatOneLineText: p.formatOneLineText,
|
||||
FormatList: p.formatList,
|
||||
},
|
||||
}
|
||||
|
||||
if c.Conf.ToSlack {
|
||||
reports = append(reports, report.SlackWriter{})
|
||||
}
|
||||
|
||||
if c.Conf.ToChatWork {
|
||||
reports = append(reports, report.ChatWorkWriter{})
|
||||
}
|
||||
|
||||
if c.Conf.ToTelegram {
|
||||
reports = append(reports, report.TelegramWriter{})
|
||||
}
|
||||
|
||||
if c.Conf.ToEmail {
|
||||
reports = append(reports, report.EMailWriter{})
|
||||
}
|
||||
|
||||
if c.Conf.ToSyslog {
|
||||
reports = append(reports, report.SyslogWriter{})
|
||||
}
|
||||
|
||||
if c.Conf.ToHTTP {
|
||||
reports = append(reports, report.HTTPRequestWriter{})
|
||||
}
|
||||
|
||||
if c.Conf.ToLocalFile {
|
||||
reports = append(reports, report.LocalFileWriter{
|
||||
CurrentDir: dir,
|
||||
if p.toSlack {
|
||||
reports = append(reports, reporter.SlackWriter{
|
||||
FormatOneLineText: p.formatOneLineText,
|
||||
})
|
||||
}
|
||||
|
||||
if c.Conf.ToS3 {
|
||||
if err := report.CheckIfBucketExists(); err != nil {
|
||||
if p.toChatWork {
|
||||
reports = append(reports, reporter.ChatWorkWriter{})
|
||||
}
|
||||
|
||||
if p.toTelegram {
|
||||
reports = append(reports, reporter.TelegramWriter{})
|
||||
}
|
||||
|
||||
if p.toEmail {
|
||||
reports = append(reports, reporter.EMailWriter{
|
||||
FormatOneEMail: p.formatOneEMail,
|
||||
FormatOneLineText: p.formatOneLineText,
|
||||
FormatList: p.formatList,
|
||||
})
|
||||
}
|
||||
|
||||
if p.toSyslog {
|
||||
reports = append(reports, reporter.SyslogWriter{})
|
||||
}
|
||||
|
||||
if p.toHTTP {
|
||||
reports = append(reports, reporter.HTTPRequestWriter{})
|
||||
}
|
||||
|
||||
if p.toLocalFile {
|
||||
reports = append(reports, reporter.LocalFileWriter{
|
||||
CurrentDir: dir,
|
||||
DiffPlus: c.Conf.DiffPlus,
|
||||
DiffMinus: c.Conf.DiffMinus,
|
||||
FormatJSON: p.formatJSON,
|
||||
FormatCsv: p.formatCsv,
|
||||
FormatFullText: p.formatFullText,
|
||||
FormatOneLineText: p.formatOneLineText,
|
||||
FormatList: p.formatList,
|
||||
Gzip: p.gzip,
|
||||
})
|
||||
}
|
||||
|
||||
if p.toS3 {
|
||||
if err := reporter.CheckIfBucketExists(); err != nil {
|
||||
util.Log.Errorf("Check if there is a bucket beforehand: %s, err: %+v",
|
||||
c.Conf.AWS.S3Bucket, err)
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
reports = append(reports, report.S3Writer{})
|
||||
reports = append(reports, reporter.S3Writer{
|
||||
FormatJSON: p.formatJSON,
|
||||
FormatFullText: p.formatFullText,
|
||||
FormatOneLineText: p.formatOneLineText,
|
||||
FormatList: p.formatList,
|
||||
Gzip: p.gzip,
|
||||
})
|
||||
}
|
||||
|
||||
if c.Conf.ToAzureBlob {
|
||||
if len(c.Conf.Azure.AccountName) == 0 {
|
||||
if p.toAzureBlob {
|
||||
// TODO refactor
|
||||
if c.Conf.Azure.AccountName == "" {
|
||||
c.Conf.Azure.AccountName = os.Getenv("AZURE_STORAGE_ACCOUNT")
|
||||
}
|
||||
|
||||
if len(c.Conf.Azure.AccountKey) == 0 {
|
||||
if c.Conf.Azure.AccountKey == "" {
|
||||
c.Conf.Azure.AccountKey = os.Getenv("AZURE_STORAGE_ACCESS_KEY")
|
||||
}
|
||||
|
||||
if len(c.Conf.Azure.ContainerName) == 0 {
|
||||
if c.Conf.Azure.ContainerName == "" {
|
||||
util.Log.Error("Azure storage container name is required with -azure-container option")
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
if err := report.CheckIfAzureContainerExists(); err != nil {
|
||||
if err := reporter.CheckIfAzureContainerExists(); err != nil {
|
||||
util.Log.Errorf("Check if there is a container beforehand: %s, err: %+v",
|
||||
c.Conf.Azure.ContainerName, err)
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
reports = append(reports, report.AzureBlobWriter{})
|
||||
reports = append(reports, reporter.AzureBlobWriter{
|
||||
FormatJSON: p.formatJSON,
|
||||
FormatFullText: p.formatFullText,
|
||||
FormatOneLineText: p.formatOneLineText,
|
||||
FormatList: p.formatList,
|
||||
Gzip: p.gzip,
|
||||
})
|
||||
}
|
||||
|
||||
for _, w := range reports {
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
c "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/report"
|
||||
"github.com/future-architect/vuls/reporter"
|
||||
"github.com/future-architect/vuls/saas"
|
||||
"github.com/future-architect/vuls/util"
|
||||
"github.com/google/subcommands"
|
||||
@@ -35,19 +35,14 @@ func (*SaaSCmd) Usage() string {
|
||||
[-log-dir=/path/to/log]
|
||||
[-http-proxy=http://192.168.0.1:8080]
|
||||
[-debug]
|
||||
[-debug-sql]
|
||||
[-quiet]
|
||||
[-no-progress]
|
||||
`
|
||||
}
|
||||
|
||||
// SetFlags set flag
|
||||
func (p *SaaSCmd) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&c.Conf.Lang, "lang", "en", "[en|ja]")
|
||||
f.BoolVar(&c.Conf.Debug, "debug", false, "debug mode")
|
||||
f.BoolVar(&c.Conf.DebugSQL, "debug-sql", false, "SQL debug mode")
|
||||
f.BoolVar(&c.Conf.Quiet, "quiet", false, "Quiet mode. No output on stdout")
|
||||
f.BoolVar(&c.Conf.NoProgress, "no-progress", false, "Suppress progress bar")
|
||||
|
||||
wd, _ := os.Getwd()
|
||||
defaultConfPath := filepath.Join(wd, "config.toml")
|
||||
@@ -72,7 +67,7 @@ func (p *SaaSCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
|
||||
dir, err := report.JSONDir(f.Args())
|
||||
dir, err := reporter.JSONDir(f.Args())
|
||||
if err != nil {
|
||||
util.Log.Errorf("Failed to read from JSON: %+v", err)
|
||||
return subcommands.ExitFailure
|
||||
@@ -84,7 +79,7 @@ func (p *SaaSCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
|
||||
}
|
||||
|
||||
var loaded models.ScanResults
|
||||
if loaded, err = report.LoadScanResults(dir); err != nil {
|
||||
if loaded, err = reporter.LoadScanResults(dir); err != nil {
|
||||
util.Log.Error(err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
@@ -108,8 +103,7 @@ func (p *SaaSCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
|
||||
|
||||
for _, r := range res {
|
||||
util.Log.Debugf("%s: %s",
|
||||
r.ServerInfo(),
|
||||
pp.Sprintf("%s", c.Conf.Servers[r.ServerName]))
|
||||
r.ServerInfo(), pp.Sprintf("%s", c.Conf.Servers[r.ServerName]))
|
||||
}
|
||||
|
||||
// Ensure UUIDs of scan target servers in config.toml
|
||||
@@ -118,7 +112,7 @@ func (p *SaaSCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
var w report.ResultWriter = saas.Writer{}
|
||||
var w reporter.ResultWriter = saas.Writer{}
|
||||
if err := w.Write(res...); err != nil {
|
||||
util.Log.Errorf("Failed to upload. err: %+v", err)
|
||||
return subcommands.ExitFailure
|
||||
|
||||
@@ -9,8 +9,9 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
c "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/scan"
|
||||
"github.com/future-architect/vuls/scanner"
|
||||
"github.com/future-architect/vuls/util"
|
||||
"github.com/google/subcommands"
|
||||
"github.com/k0kubun/pp"
|
||||
@@ -22,6 +23,7 @@ type ScanCmd struct {
|
||||
askKeyPassword bool
|
||||
timeoutSec int
|
||||
scanTimeoutSec int
|
||||
cacheDBPath string
|
||||
}
|
||||
|
||||
// Name return subcommand name
|
||||
@@ -38,7 +40,6 @@ func (*ScanCmd) Usage() string {
|
||||
[-results-dir=/path/to/results]
|
||||
[-log-dir=/path/to/log]
|
||||
[-cachedb-path=/path/to/cache.db]
|
||||
[-ssh-native-insecure]
|
||||
[-http-proxy=http://192.168.0.1:8080]
|
||||
[-ask-key-password]
|
||||
[-timeout=300]
|
||||
@@ -70,12 +71,9 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&c.Conf.LogDir, "log-dir", defaultLogDir, "/path/to/log")
|
||||
|
||||
defaultCacheDBPath := filepath.Join(wd, "cache.db")
|
||||
f.StringVar(&c.Conf.CacheDBPath, "cachedb-path", defaultCacheDBPath,
|
||||
f.StringVar(&p.cacheDBPath, "cachedb-path", defaultCacheDBPath,
|
||||
"/path/to/cache.db (local cache of changelog for Ubuntu/Debian)")
|
||||
|
||||
f.BoolVar(&c.Conf.SSHNative, "ssh-native-insecure", false,
|
||||
"Use Native Go implementation of SSH. Default: Use the external command")
|
||||
|
||||
f.StringVar(&c.Conf.HTTPProxy, "http-proxy", "",
|
||||
"http://proxy-url:port (default: empty)")
|
||||
|
||||
@@ -103,10 +101,18 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
|
||||
util.Log = util.NewCustomLogger(c.ServerInfo{})
|
||||
|
||||
if err := mkdirDotVuls(); err != nil {
|
||||
util.Log.Errorf("Failed to create .vuls. err: %+v", err)
|
||||
util.Log.Errorf("Failed to create $HOME/.vuls err: %+v", err)
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
|
||||
if len(p.cacheDBPath) != 0 {
|
||||
if ok, _ := govalidator.IsFilePath(p.cacheDBPath); !ok {
|
||||
util.Log.Errorf("Cache DB path must be a *Absolute* file path. -cache-dbpath: %s",
|
||||
p.cacheDBPath)
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
}
|
||||
|
||||
var keyPass string
|
||||
var err error
|
||||
if p.askKeyPassword {
|
||||
@@ -161,6 +167,7 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
}
|
||||
// if scan target servers are specified by args, set to the config
|
||||
if 0 < len(servernames) {
|
||||
c.Conf.Servers = target
|
||||
}
|
||||
@@ -171,27 +178,18 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
|
||||
util.Log.Info("Detecting Server/Container OS... ")
|
||||
if err := scan.InitServers(p.timeoutSec); err != nil {
|
||||
util.Log.Errorf("Failed to init servers: %+v", err)
|
||||
s := scanner.Scanner{
|
||||
TimeoutSec: p.timeoutSec,
|
||||
ScanTimeoutSec: p.scanTimeoutSec,
|
||||
CacheDBPath: p.cacheDBPath,
|
||||
Targets: target,
|
||||
}
|
||||
|
||||
if err := s.Scan(); err != nil {
|
||||
util.Log.Errorf("Failed to scan: %+v", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
util.Log.Info("Checking Scan Modes... ")
|
||||
if err := scan.CheckScanModes(); err != nil {
|
||||
util.Log.Errorf("Fix config.toml. err: %+v", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
util.Log.Info("Detecting Platforms... ")
|
||||
scan.DetectPlatforms(p.timeoutSec)
|
||||
util.Log.Info("Detecting IPS identifiers... ")
|
||||
scan.DetectIPSs(p.timeoutSec)
|
||||
|
||||
if err := scan.Scan(p.scanTimeoutSec); err != nil {
|
||||
util.Log.Errorf("Failed to scan. err: %+v", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
fmt.Printf("\n\n\n")
|
||||
fmt.Println("To view the detail, vuls tui is useful.")
|
||||
fmt.Println("To send a report, run vuls report -h.")
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
c "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/report"
|
||||
"github.com/future-architect/vuls/detector"
|
||||
"github.com/future-architect/vuls/server"
|
||||
"github.com/future-architect/vuls/util"
|
||||
"github.com/google/subcommands"
|
||||
@@ -22,8 +22,9 @@ import (
|
||||
|
||||
// ServerCmd is subcommand for server
|
||||
type ServerCmd struct {
|
||||
configPath string
|
||||
listen string
|
||||
configPath string
|
||||
listen string
|
||||
toLocalFile bool
|
||||
}
|
||||
|
||||
// Name return subcommand name
|
||||
@@ -43,7 +44,6 @@ func (*ServerCmd) Usage() string {
|
||||
[-ignore-unscored-cves]
|
||||
[-ignore-unfixed]
|
||||
[-to-localfile]
|
||||
[-format-json]
|
||||
[-http-proxy=http://192.168.0.1:8080]
|
||||
[-debug]
|
||||
[-debug-sql]
|
||||
@@ -81,9 +81,7 @@ func (p *ServerCmd) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&c.Conf.HTTPProxy, "http-proxy", "",
|
||||
"http://proxy-url:port (default: empty)")
|
||||
|
||||
f.BoolVar(&c.Conf.FormatJSON, "format-json", false, "JSON format")
|
||||
|
||||
f.BoolVar(&c.Conf.ToLocalFile, "to-localfile", false, "Write report to localfile")
|
||||
f.BoolVar(&p.toLocalFile, "to-localfile", false, "Write report to localfile")
|
||||
f.StringVar(&p.listen, "listen", "localhost:5515",
|
||||
"host:port (default: localhost:5515)")
|
||||
}
|
||||
@@ -119,7 +117,7 @@ func (p *ServerCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}
|
||||
}
|
||||
}
|
||||
|
||||
dbclient, locked, err := report.NewDBClient(report.DBClientConf{
|
||||
dbclient, locked, err := detector.NewDBClient(detector.DBClientConf{
|
||||
CveDictCnf: c.Conf.CveDict,
|
||||
OvalDictCnf: c.Conf.OvalDict,
|
||||
GostCnf: c.Conf.Gost,
|
||||
@@ -139,7 +137,10 @@ func (p *ServerCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}
|
||||
|
||||
defer dbclient.CloseDB()
|
||||
|
||||
http.Handle("/vuls", server.VulsHandler{DBclient: *dbclient})
|
||||
http.Handle("/vuls", server.VulsHandler{
|
||||
DBclient: *dbclient,
|
||||
ToLocalFile: p.toLocalFile,
|
||||
})
|
||||
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "ok")
|
||||
})
|
||||
|
||||
@@ -11,8 +11,10 @@ import (
|
||||
"github.com/aquasecurity/trivy/pkg/utils"
|
||||
"github.com/future-architect/vuls/config"
|
||||
c "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/detector"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/report"
|
||||
"github.com/future-architect/vuls/reporter"
|
||||
"github.com/future-architect/vuls/tui"
|
||||
"github.com/future-architect/vuls/util"
|
||||
"github.com/google/subcommands"
|
||||
)
|
||||
@@ -115,9 +117,9 @@ func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) s
|
||||
var dir string
|
||||
var err error
|
||||
if c.Conf.DiffPlus || c.Conf.DiffMinus {
|
||||
dir, err = report.JSONDir([]string{})
|
||||
dir, err = reporter.JSONDir([]string{})
|
||||
} else {
|
||||
dir, err = report.JSONDir(f.Args())
|
||||
dir, err = reporter.JSONDir(f.Args())
|
||||
}
|
||||
if err != nil {
|
||||
util.Log.Errorf("Failed to read from JSON. err: %+v", err)
|
||||
@@ -130,7 +132,7 @@ func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) s
|
||||
}
|
||||
|
||||
var res models.ScanResults
|
||||
if res, err = report.LoadScanResults(dir); err != nil {
|
||||
if res, err = reporter.LoadScanResults(dir); err != nil {
|
||||
util.Log.Error(err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
@@ -154,7 +156,7 @@ func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) s
|
||||
}
|
||||
}
|
||||
|
||||
dbclient, locked, err := report.NewDBClient(report.DBClientConf{
|
||||
dbclient, locked, err := detector.NewDBClient(detector.DBClientConf{
|
||||
CveDictCnf: c.Conf.CveDict,
|
||||
OvalDictCnf: c.Conf.OvalDict,
|
||||
GostCnf: c.Conf.Gost,
|
||||
@@ -174,7 +176,7 @@ func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) s
|
||||
|
||||
defer dbclient.CloseDB()
|
||||
|
||||
if res, err = report.FillCveInfos(*dbclient, res, dir); err != nil {
|
||||
if res, err = detector.Detect(*dbclient, res, dir); err != nil {
|
||||
util.Log.Error(err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
@@ -186,5 +188,5 @@ func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) s
|
||||
}
|
||||
}
|
||||
|
||||
return report.RunTui(res)
|
||||
return tui.RunTui(res)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package report
|
||||
package tui
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -826,7 +826,7 @@ func setChangelogLayout(g *gocui.Gui) error {
|
||||
"=============",
|
||||
)
|
||||
for _, alert := range vinfo.AlertDict.Ja {
|
||||
if config.Conf.Lang == "ja" {
|
||||
if r.Lang == "ja" {
|
||||
lines = append(lines, fmt.Sprintf("* [%s](%s)", alert.Title, alert.URL))
|
||||
} else {
|
||||
lines = append(lines, fmt.Sprintf("* [JPCERT](%s)", alert.URL))
|
||||
@@ -834,7 +834,7 @@ func setChangelogLayout(g *gocui.Gui) error {
|
||||
}
|
||||
}
|
||||
|
||||
if currentScanResult.IsDeepScanMode() {
|
||||
if currentScanResult.Config.Scan.Servers[currentScanResult.ServerName].Mode.IsDeep() {
|
||||
lines = append(lines, "\n",
|
||||
"ChangeLogs",
|
||||
"==========",
|
||||
@@ -894,7 +894,7 @@ func detailLines() (string, error) {
|
||||
|
||||
vinfo := vinfos[currentVinfo]
|
||||
links := []string{}
|
||||
for _, r := range vinfo.CveContents.PrimarySrcURLs(config.Conf.Lang, r.Family, vinfo.CveID) {
|
||||
for _, r := range vinfo.CveContents.PrimarySrcURLs(r.Lang, r.Family, vinfo.CveID) {
|
||||
links = append(links, r.Value)
|
||||
}
|
||||
|
||||
@@ -934,7 +934,7 @@ func detailLines() (string, error) {
|
||||
}
|
||||
|
||||
table := uitable.New()
|
||||
table.MaxColWidth = maxColWidth
|
||||
table.MaxColWidth = 100
|
||||
table.Wrap = true
|
||||
scores := append(vinfo.Cvss3Scores(), vinfo.Cvss2Scores()...)
|
||||
var cols []interface{}
|
||||
Reference in New Issue
Block a user