Add: source code
This commit is contained in:
		
							
								
								
									
										9
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
.vscode
 | 
			
		||||
coverage.out
 | 
			
		||||
issues/
 | 
			
		||||
*.txt
 | 
			
		||||
vendor/
 | 
			
		||||
log/
 | 
			
		||||
.gitmodules
 | 
			
		||||
vuls
 | 
			
		||||
*.sqlite3
 | 
			
		||||
							
								
								
									
										3
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
 | 
			
		||||
TODO
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										52
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
.PHONY: \
 | 
			
		||||
	all \
 | 
			
		||||
	vendor \
 | 
			
		||||
	lint \
 | 
			
		||||
	vet \
 | 
			
		||||
	fmt \
 | 
			
		||||
	fmtcheck \
 | 
			
		||||
	pretest \
 | 
			
		||||
	test \
 | 
			
		||||
	integration \
 | 
			
		||||
	cov \
 | 
			
		||||
	clean
 | 
			
		||||
 | 
			
		||||
SRCS = $(shell git ls-files '*.go')
 | 
			
		||||
PKGS = ./. ./db ./config ./models ./report ./cveapi ./scan ./util ./commands
 | 
			
		||||
 | 
			
		||||
all: test
 | 
			
		||||
 | 
			
		||||
vendor:
 | 
			
		||||
	@ go get -v github.com/mjibson/party
 | 
			
		||||
	party -d external -c -u
 | 
			
		||||
 | 
			
		||||
lint:
 | 
			
		||||
	@ go get -v github.com/golang/lint/golint
 | 
			
		||||
	$(foreach file,$(SRCS),golint $(file) || exit;)
 | 
			
		||||
 | 
			
		||||
vet:
 | 
			
		||||
	@-go get -v golang.org/x/tools/cmd/vet
 | 
			
		||||
	$(foreach pkg,$(PKGS),go vet $(pkg);)
 | 
			
		||||
 | 
			
		||||
fmt:
 | 
			
		||||
	gofmt -w $(SRCS)
 | 
			
		||||
 | 
			
		||||
fmtcheck:
 | 
			
		||||
	$(foreach file,$(SRCS),gofmt -d $(file);)
 | 
			
		||||
 | 
			
		||||
pretest: lint vet fmtcheck
 | 
			
		||||
 | 
			
		||||
test: pretest
 | 
			
		||||
	$(foreach pkg,$(PKGS),go test -v $(pkg) || exit;)
 | 
			
		||||
 | 
			
		||||
unused :
 | 
			
		||||
	$(foreach pkg,$(PKGS),unused $(pkg);)
 | 
			
		||||
 | 
			
		||||
cov:
 | 
			
		||||
	@ go get -v github.com/axw/gocov/gocov
 | 
			
		||||
	@ go get golang.org/x/tools/cmd/cover
 | 
			
		||||
	gocov test | gocov report
 | 
			
		||||
 | 
			
		||||
clean:
 | 
			
		||||
	$(foreach pkg,$(PKGS),go clean $(pkg) || exit;)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										158
									
								
								commands/discover.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								commands/discover.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,158 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"text/template"
 | 
			
		||||
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	ps "github.com/kotakanbe/go-pingscanner"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// DiscoverCmd is Subcommand of host discovery mode
 | 
			
		||||
type DiscoverCmd struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
func (*DiscoverCmd) Name() string { return "discover" }
 | 
			
		||||
 | 
			
		||||
// Synopsis return synopsis
 | 
			
		||||
func (*DiscoverCmd) Synopsis() string { return "Host discovery in the CIDR." }
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*DiscoverCmd) Usage() string {
 | 
			
		||||
	return `discover:
 | 
			
		||||
	discover 192.168.0.0/24
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *DiscoverCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *DiscoverCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	// validate
 | 
			
		||||
	if len(f.Args()) == 0 {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, cidr := range f.Args() {
 | 
			
		||||
		scanner := ps.PingScanner{
 | 
			
		||||
			CIDR: cidr,
 | 
			
		||||
			PingOptions: []string{
 | 
			
		||||
				"-c1",
 | 
			
		||||
				"-t1",
 | 
			
		||||
			},
 | 
			
		||||
			NumOfConcurrency: 100,
 | 
			
		||||
		}
 | 
			
		||||
		hosts, err := scanner.Scan()
 | 
			
		||||
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Errorf("Host Discovery failed. err: %s", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(hosts) < 1 {
 | 
			
		||||
			logrus.Errorf("Active hosts not found in %s.", cidr)
 | 
			
		||||
			return subcommands.ExitSuccess
 | 
			
		||||
		} else if err := printConfigToml(hosts); err != nil {
 | 
			
		||||
			logrus.Errorf("Failed to parse template. err: %s", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Output the tmeplate of config.toml
 | 
			
		||||
func printConfigToml(ips []string) (err error) {
 | 
			
		||||
	const tomlTempale = `
 | 
			
		||||
[slack]
 | 
			
		||||
hookURL      = "https://hooks.slack.com/services/abc123/defghijklmnopqrstuvwxyz"
 | 
			
		||||
channel      = "#channel-name"
 | 
			
		||||
#channel      = "#{servername}"
 | 
			
		||||
iconEmoji    = ":ghost:"
 | 
			
		||||
authUser     = "username"
 | 
			
		||||
notifyUsers  = ["@username"]
 | 
			
		||||
 | 
			
		||||
[mail]
 | 
			
		||||
smtpAddr      = "smtp.gmail.com"
 | 
			
		||||
smtpPort      = 465
 | 
			
		||||
user          = "username"
 | 
			
		||||
password      = "password"
 | 
			
		||||
from          = "from@address.com"
 | 
			
		||||
to            = ["to@address.com"]
 | 
			
		||||
cc            = ["cc@address.com"]
 | 
			
		||||
subjectPrefix = "[vuls]"
 | 
			
		||||
 | 
			
		||||
[default]
 | 
			
		||||
#port        = "22"
 | 
			
		||||
#user        = "username"
 | 
			
		||||
#password    = "password"
 | 
			
		||||
#keyPath     = "/home/username/.ssh/id_rsa"
 | 
			
		||||
#keyPassword = "password"
 | 
			
		||||
 | 
			
		||||
[servers]
 | 
			
		||||
{{- $names:=  .Names}}
 | 
			
		||||
{{range $i, $ip := .IPs}}
 | 
			
		||||
[servers.{{index $names $i}}]
 | 
			
		||||
host         = "{{$ip}}"
 | 
			
		||||
#port        = "22"
 | 
			
		||||
#user        = "root"
 | 
			
		||||
#password    = "password"
 | 
			
		||||
#keyPath     = "/home/username/.ssh/id_rsa"
 | 
			
		||||
#keyPassword = "password"
 | 
			
		||||
#cpeNames = [
 | 
			
		||||
#  "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
 | 
			
		||||
#]
 | 
			
		||||
{{end}}
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
	var tpl *template.Template
 | 
			
		||||
	if tpl, err = template.New("tempalte").Parse(tomlTempale); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	type activeHosts struct {
 | 
			
		||||
		IPs   []string
 | 
			
		||||
		Names []string
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	a := activeHosts{IPs: ips}
 | 
			
		||||
	names := []string{}
 | 
			
		||||
	for _, ip := range ips {
 | 
			
		||||
		// TOML section header must not contain "."
 | 
			
		||||
		name := strings.Replace(ip, ".", "-", -1)
 | 
			
		||||
		names = append(names, name)
 | 
			
		||||
	}
 | 
			
		||||
	a.Names = names
 | 
			
		||||
 | 
			
		||||
	fmt.Println("# Create config.toml using below and then ./vuls --config=/path/to/config.toml")
 | 
			
		||||
	if err = tpl.Execute(os.Stdout, a); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										131
									
								
								commands/prepare.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								commands/prepare.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,131 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"flag"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/scan"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// PrepareCmd is Subcommand of host discovery mode
 | 
			
		||||
type PrepareCmd struct {
 | 
			
		||||
	debug      bool
 | 
			
		||||
	configPath string
 | 
			
		||||
 | 
			
		||||
	useUnattendedUpgrades bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
func (*PrepareCmd) Name() string { return "prepare" }
 | 
			
		||||
 | 
			
		||||
// Synopsis return synopsis
 | 
			
		||||
func (*PrepareCmd) Synopsis() string {
 | 
			
		||||
	//  return "Install packages Ubuntu: unattended-upgrade, CentOS: yum-plugin-security)"
 | 
			
		||||
	return `Install required packages to scan.
 | 
			
		||||
				CentOS: yum-plugin-security, yum-plugin-changelog
 | 
			
		||||
				Amazon: None
 | 
			
		||||
				RHEL:   TODO
 | 
			
		||||
				Ubuntu: None
 | 
			
		||||
 | 
			
		||||
	`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*PrepareCmd) Usage() string {
 | 
			
		||||
	return `prepare:
 | 
			
		||||
	prepare [-config=/path/to/config.toml] [-debug]
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *PrepareCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.debug, "debug", false, "debug mode")
 | 
			
		||||
 | 
			
		||||
	defaultConfPath := os.Getenv("PWD") + "/config.toml"
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.useUnattendedUpgrades,
 | 
			
		||||
		"use-unattended-upgrades",
 | 
			
		||||
		false,
 | 
			
		||||
		"[Depricated] For Ubuntu, install unattended-upgrades",
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *PrepareCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	logrus.Infof("Begin Preparing (config: %s)", p.configPath)
 | 
			
		||||
 | 
			
		||||
	err := c.Load(p.configPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	target := make(map[string]c.ServerInfo)
 | 
			
		||||
	for _, arg := range f.Args() {
 | 
			
		||||
		found := false
 | 
			
		||||
		for servername, info := range c.Conf.Servers {
 | 
			
		||||
			if servername == arg {
 | 
			
		||||
				target[servername] = info
 | 
			
		||||
				found = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !found {
 | 
			
		||||
			logrus.Errorf("%s is not in config", arg)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(f.Args()) {
 | 
			
		||||
		c.Conf.Servers = target
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
	c.Conf.UseUnattendedUpgrades = p.useUnattendedUpgrades
 | 
			
		||||
 | 
			
		||||
	// Set up custom logger
 | 
			
		||||
	logger := util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	logger.Info("Detecting OS... ")
 | 
			
		||||
	err = scan.InitServers(logger)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Errorf("Failed to init servers. err: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logger.Info("Installing...")
 | 
			
		||||
	if errs := scan.Prepare(); 0 < len(errs) {
 | 
			
		||||
		for _, e := range errs {
 | 
			
		||||
			logger.Errorf("Failed: %s.", e)
 | 
			
		||||
		}
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logger.Info("Success")
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										241
									
								
								commands/scan.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								commands/scan.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,241 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"flag"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/cveapi"
 | 
			
		||||
	"github.com/future-architect/vuls/db"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
	"github.com/future-architect/vuls/scan"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ScanCmd is Subcommand of host discovery mode
 | 
			
		||||
type ScanCmd struct {
 | 
			
		||||
	lang     string
 | 
			
		||||
	debug    bool
 | 
			
		||||
	debugSQL bool
 | 
			
		||||
 | 
			
		||||
	configPath string
 | 
			
		||||
 | 
			
		||||
	dbpath           string
 | 
			
		||||
	cveDictionaryURL string
 | 
			
		||||
	cvssScoreOver    float64
 | 
			
		||||
	httpProxy        string
 | 
			
		||||
 | 
			
		||||
	useYumPluginSecurity  bool
 | 
			
		||||
	useUnattendedUpgrades bool
 | 
			
		||||
 | 
			
		||||
	// reporting
 | 
			
		||||
	reportSlack bool
 | 
			
		||||
	reportMail  bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
func (*ScanCmd) Name() string { return "scan" }
 | 
			
		||||
 | 
			
		||||
// Synopsis return synopsis
 | 
			
		||||
func (*ScanCmd) Synopsis() string { return "Scan vulnerabilities." }
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*ScanCmd) Usage() string {
 | 
			
		||||
	return `scan:
 | 
			
		||||
	scan
 | 
			
		||||
		[-lang=en|ja]
 | 
			
		||||
		[-config=/path/to/config.toml]
 | 
			
		||||
		[-dbpath=/path/to/vuls.sqlite3]
 | 
			
		||||
		[-cve-dictionary-url=http://127.0.0.1:1323]
 | 
			
		||||
		[-cvss-over=7]
 | 
			
		||||
		[-report-slack]
 | 
			
		||||
		[-report-mail]
 | 
			
		||||
		[-http-proxy=http://192.168.0.1:8080]
 | 
			
		||||
		[-debug]
 | 
			
		||||
		[-debug-sql]
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	f.StringVar(&p.lang, "lang", "en", "[en|ja]")
 | 
			
		||||
	f.BoolVar(&p.debug, "debug", false, "debug mode")
 | 
			
		||||
	f.BoolVar(&p.debugSQL, "debug-sql", false, "SQL debug mode")
 | 
			
		||||
 | 
			
		||||
	defaultConfPath := os.Getenv("PWD") + "/config.toml"
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	defaultDBPath := os.Getenv("PWD") + "/vuls.sqlite3"
 | 
			
		||||
	f.StringVar(&p.dbpath, "dbpath", defaultDBPath, "/path/to/sqlite3")
 | 
			
		||||
 | 
			
		||||
	defaultURL := "http://127.0.0.1:1323"
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cveDictionaryURL,
 | 
			
		||||
		"cve-dictionary-url",
 | 
			
		||||
		defaultURL,
 | 
			
		||||
		"http://CVE.Dictionary")
 | 
			
		||||
 | 
			
		||||
	f.Float64Var(
 | 
			
		||||
		&p.cvssScoreOver,
 | 
			
		||||
		"cvss-over",
 | 
			
		||||
		0,
 | 
			
		||||
		"-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.httpProxy,
 | 
			
		||||
		"http-proxy",
 | 
			
		||||
		"",
 | 
			
		||||
		"http://proxy-url:port (default: empty)",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.reportSlack, "report-slack", false, "Slack report")
 | 
			
		||||
	f.BoolVar(&p.reportMail, "report-mail", false, "Email report")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.useYumPluginSecurity,
 | 
			
		||||
		"use-yum-plugin-security",
 | 
			
		||||
		false,
 | 
			
		||||
		"[Depricated] For CentOS 5. Scan by yum-plugin-security or not (use yum check-update by default)",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.useUnattendedUpgrades,
 | 
			
		||||
		"use-unattended-upgrades",
 | 
			
		||||
		false,
 | 
			
		||||
		"[Depricated] For Ubuntu. Scan by unattended-upgrades or not (use apt-get upgrade --dry-run by default)",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
 | 
			
		||||
	logrus.Infof("Begin scannig (config: %s)", p.configPath)
 | 
			
		||||
	err := c.Load(p.configPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	target := make(map[string]c.ServerInfo)
 | 
			
		||||
	for _, arg := range f.Args() {
 | 
			
		||||
		found := false
 | 
			
		||||
		for servername, info := range c.Conf.Servers {
 | 
			
		||||
			if servername == arg {
 | 
			
		||||
				target[servername] = info
 | 
			
		||||
				found = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !found {
 | 
			
		||||
			logrus.Errorf("%s is not in config", arg)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(f.Args()) {
 | 
			
		||||
		c.Conf.Servers = target
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.Lang = p.lang
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
	c.Conf.DebugSQL = p.debugSQL
 | 
			
		||||
 | 
			
		||||
	// logger
 | 
			
		||||
	Log := util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	// report
 | 
			
		||||
	reports := []report.ResultWriter{
 | 
			
		||||
		report.TextWriter{},
 | 
			
		||||
		report.LogrusWriter{},
 | 
			
		||||
	}
 | 
			
		||||
	if p.reportSlack {
 | 
			
		||||
		reports = append(reports, report.SlackWriter{})
 | 
			
		||||
	}
 | 
			
		||||
	if p.reportMail {
 | 
			
		||||
		reports = append(reports, report.MailWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.DBPath = p.dbpath
 | 
			
		||||
	c.Conf.CveDictionaryURL = p.cveDictionaryURL
 | 
			
		||||
	c.Conf.HTTPProxy = p.httpProxy
 | 
			
		||||
	c.Conf.UseYumPluginSecurity = p.useYumPluginSecurity
 | 
			
		||||
	c.Conf.UseUnattendedUpgrades = p.useUnattendedUpgrades
 | 
			
		||||
 | 
			
		||||
	Log.Info("Validating Config...")
 | 
			
		||||
	if !c.Conf.Validate() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ok, err := cveapi.CveClient.CheckHealth(); !ok {
 | 
			
		||||
		Log.Errorf("CVE HTTP server is not running. %#v", cveapi.CveClient)
 | 
			
		||||
		Log.Fatal(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Detecting OS... ")
 | 
			
		||||
	err = scan.InitServers(Log)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		Log.Errorf("Failed to init servers. err: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Scanning vulnerabilities... ")
 | 
			
		||||
	if errs := scan.Scan(); 0 < len(errs) {
 | 
			
		||||
		for _, e := range errs {
 | 
			
		||||
			Log.Errorf("Failed to scan. err: %s.", e)
 | 
			
		||||
		}
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	scanResults, err := scan.GetScanResults()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		Log.Fatal(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Reporting...")
 | 
			
		||||
	filtered := scanResults.FilterByCvssOver()
 | 
			
		||||
	for _, w := range reports {
 | 
			
		||||
		if err := w.Write(filtered); err != nil {
 | 
			
		||||
			Log.Fatalf("Failed to output report, err: %s", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Insert to DB...")
 | 
			
		||||
	if err := db.OpenDB(); err != nil {
 | 
			
		||||
		Log.Errorf("Failed to open DB. datafile: %s, err: %s", c.Conf.DBPath, err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.MigrateDB(); err != nil {
 | 
			
		||||
		Log.Errorf("Failed to migrate. err: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := db.Insert(scanResults); err != nil {
 | 
			
		||||
		Log.Fatalf("Failed to insert. dbpath: %s, err: %s", c.Conf.DBPath, err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										68
									
								
								commands/tui.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								commands/tui.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TuiCmd is Subcommand of host discovery mode
 | 
			
		||||
type TuiCmd struct {
 | 
			
		||||
	lang     string
 | 
			
		||||
	debugSQL bool
 | 
			
		||||
	dbpath   string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
func (*TuiCmd) Name() string { return "tui" }
 | 
			
		||||
 | 
			
		||||
// Synopsis return synopsis
 | 
			
		||||
func (*TuiCmd) Synopsis() string { return "Run Tui view to anayze vulnerabilites." }
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*TuiCmd) Usage() string {
 | 
			
		||||
	return `tui:
 | 
			
		||||
	tui [-dbpath=/path/to/vuls.sqlite3]
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *TuiCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	//  f.StringVar(&p.lang, "lang", "en", "[en|ja]")
 | 
			
		||||
	f.BoolVar(&p.debugSQL, "debug-sql", false, "debug SQL")
 | 
			
		||||
 | 
			
		||||
	defaultDBPath := os.Getenv("PWD") + "/vuls.sqlite3"
 | 
			
		||||
	f.StringVar(&p.dbpath, "dbpath", defaultDBPath,
 | 
			
		||||
		fmt.Sprintf("/path/to/sqlite3 (default: %s)", defaultDBPath))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	c.Conf.Lang = "en"
 | 
			
		||||
	c.Conf.DebugSQL = p.debugSQL
 | 
			
		||||
	c.Conf.DBPath = p.dbpath
 | 
			
		||||
	return report.RunTui()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								config/color.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								config/color.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	// Colors has ansi color list
 | 
			
		||||
	Colors = []string{
 | 
			
		||||
		"\033[32m", // green
 | 
			
		||||
		"\033[33m", // yellow
 | 
			
		||||
		"\033[36m", // cyan
 | 
			
		||||
		"\033[35m", // magenta
 | 
			
		||||
		"\033[31m", // red
 | 
			
		||||
		"\033[34m", // blue
 | 
			
		||||
	}
 | 
			
		||||
	// ResetColor is reset color
 | 
			
		||||
	ResetColor = "\033[0m"
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										221
									
								
								config/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								config/config.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,221 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	valid "github.com/asaskevich/govalidator"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Conf has Configuration
 | 
			
		||||
var Conf Config
 | 
			
		||||
 | 
			
		||||
//Config is struct of Configuration
 | 
			
		||||
type Config struct {
 | 
			
		||||
	Debug    bool
 | 
			
		||||
	DebugSQL bool
 | 
			
		||||
	Lang     string
 | 
			
		||||
 | 
			
		||||
	Mail    smtpConf
 | 
			
		||||
	Slack   SlackConf
 | 
			
		||||
	Default ServerInfo
 | 
			
		||||
	Servers map[string]ServerInfo
 | 
			
		||||
 | 
			
		||||
	CveDictionaryURL string `valid:"url"`
 | 
			
		||||
 | 
			
		||||
	CvssScoreOver float64
 | 
			
		||||
	HTTPProxy     string `valid:"url"`
 | 
			
		||||
	DBPath        string
 | 
			
		||||
	//  CpeNames      []string
 | 
			
		||||
	//  SummaryMode          bool
 | 
			
		||||
	UseYumPluginSecurity  bool
 | 
			
		||||
	UseUnattendedUpgrades bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate configuration
 | 
			
		||||
func (c Config) Validate() bool {
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
 | 
			
		||||
	if len(c.DBPath) != 0 {
 | 
			
		||||
		if ok, _ := valid.IsFilePath(c.DBPath); !ok {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				"SQLite3 DB path must be a *Absolute* file path. dbpath: %s", c.DBPath))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := valid.ValidateStruct(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if mailerrs := c.Mail.Validate(); 0 < len(mailerrs) {
 | 
			
		||||
		errs = append(errs, mailerrs...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if slackerrs := c.Slack.Validate(); 0 < len(slackerrs) {
 | 
			
		||||
		errs = append(errs, slackerrs...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, err := range errs {
 | 
			
		||||
		log.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return len(errs) == 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// smtpConf is smtp config
 | 
			
		||||
type smtpConf struct {
 | 
			
		||||
	SMTPAddr string
 | 
			
		||||
	SMTPPort string `valid:"port"`
 | 
			
		||||
 | 
			
		||||
	User          string
 | 
			
		||||
	Password      string
 | 
			
		||||
	From          string
 | 
			
		||||
	To            []string
 | 
			
		||||
	Cc            []string
 | 
			
		||||
	SubjectPrefix string
 | 
			
		||||
 | 
			
		||||
	UseThisTime bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func checkEmails(emails []string) (errs []error) {
 | 
			
		||||
	for _, addr := range emails {
 | 
			
		||||
		if len(addr) == 0 {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if ok := valid.IsEmail(addr); !ok {
 | 
			
		||||
			errs = append(errs, fmt.Errorf("Invalid email address. email: %s", addr))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate SMTP configuration
 | 
			
		||||
func (c *smtpConf) Validate() (errs []error) {
 | 
			
		||||
 | 
			
		||||
	if !c.UseThisTime {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check Emails fromat
 | 
			
		||||
	emails := []string{}
 | 
			
		||||
	emails = append(emails, c.From)
 | 
			
		||||
	emails = append(emails, c.To...)
 | 
			
		||||
	emails = append(emails, c.Cc...)
 | 
			
		||||
 | 
			
		||||
	if emailErrs := checkEmails(emails); 0 < len(emailErrs) {
 | 
			
		||||
		errs = append(errs, emailErrs...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.SMTPAddr) == 0 {
 | 
			
		||||
		errs = append(errs, fmt.Errorf("smtpAddr must not be empty"))
 | 
			
		||||
	}
 | 
			
		||||
	if len(c.SMTPPort) == 0 {
 | 
			
		||||
		errs = append(errs, fmt.Errorf("smtpPort must not be empty"))
 | 
			
		||||
	}
 | 
			
		||||
	if len(c.To) == 0 {
 | 
			
		||||
		errs = append(errs, fmt.Errorf("To required at least one address"))
 | 
			
		||||
	}
 | 
			
		||||
	if len(c.From) == 0 {
 | 
			
		||||
		errs = append(errs, fmt.Errorf("From required at least one address"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := valid.ValidateStruct(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SlackConf is slack config
 | 
			
		||||
type SlackConf struct {
 | 
			
		||||
	HookURL   string `valid:"url"`
 | 
			
		||||
	Channel   string `json:"channel"`
 | 
			
		||||
	IconEmoji string `json:"icon_emoji"`
 | 
			
		||||
	AuthUser  string `json:"username"`
 | 
			
		||||
 | 
			
		||||
	NotifyUsers []string
 | 
			
		||||
	Text        string `json:"text"`
 | 
			
		||||
 | 
			
		||||
	UseThisTime bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate validates configuration
 | 
			
		||||
func (c *SlackConf) Validate() (errs []error) {
 | 
			
		||||
 | 
			
		||||
	if !c.UseThisTime {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.HookURL) == 0 {
 | 
			
		||||
		errs = append(errs, fmt.Errorf("hookURL must not be empty"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.Channel) == 0 {
 | 
			
		||||
		errs = append(errs, fmt.Errorf("channel must not be empty"))
 | 
			
		||||
	} else {
 | 
			
		||||
		if !(strings.HasPrefix(c.Channel, "#") ||
 | 
			
		||||
			c.Channel == "${servername}") {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				"channel's prefix must be '#', channel: %s", c.Channel))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.AuthUser) == 0 {
 | 
			
		||||
		errs = append(errs, fmt.Errorf("authUser must not be empty"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := valid.ValidateStruct(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO check if slack configration is valid
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ServerInfo has SSH Info, additional CPE packages to scan.
 | 
			
		||||
type ServerInfo struct {
 | 
			
		||||
	ServerName  string
 | 
			
		||||
	User        string
 | 
			
		||||
	Password    string
 | 
			
		||||
	Host        string
 | 
			
		||||
	Port        string
 | 
			
		||||
	KeyPath     string
 | 
			
		||||
	KeyPassword string
 | 
			
		||||
	SudoOpt     SudoOption
 | 
			
		||||
 | 
			
		||||
	CpeNames []string
 | 
			
		||||
 | 
			
		||||
	// DebugLog Color
 | 
			
		||||
	LogMsgAnsiColor string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SudoOption is flag of sudo option.
 | 
			
		||||
type SudoOption struct {
 | 
			
		||||
 | 
			
		||||
	// echo pass | sudo -S ls
 | 
			
		||||
	ExecBySudo bool
 | 
			
		||||
 | 
			
		||||
	// echo pass | sudo sh -C 'ls'
 | 
			
		||||
	ExecBySudoSh bool
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								config/jsonloader.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								config/jsonloader.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import "fmt"
 | 
			
		||||
 | 
			
		||||
// JSONLoader loads configuration
 | 
			
		||||
type JSONLoader struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Load load the configuraiton JSON file specified by path arg.
 | 
			
		||||
func (c JSONLoader) Load(path string) (err error) {
 | 
			
		||||
	return fmt.Errorf("Not implement yet")
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								config/loader.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								config/loader.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
// Load loads configuration
 | 
			
		||||
func Load(path string) error {
 | 
			
		||||
 | 
			
		||||
	//TODO if path's suffix .toml
 | 
			
		||||
	var loader Loader
 | 
			
		||||
	loader = TOMLLoader{}
 | 
			
		||||
 | 
			
		||||
	return loader.Load(path)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Loader is interface of concrete loader
 | 
			
		||||
type Loader interface {
 | 
			
		||||
	Load(string) error
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										98
									
								
								config/tomlloader.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								config/tomlloader.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,98 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/BurntSushi/toml"
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TOMLLoader loads config
 | 
			
		||||
type TOMLLoader struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Load load the configuraiton TOML file specified by path arg.
 | 
			
		||||
func (c TOMLLoader) Load(pathToToml string) (err error) {
 | 
			
		||||
	var conf Config
 | 
			
		||||
	if _, err := toml.DecodeFile(pathToToml, &conf); err != nil {
 | 
			
		||||
		log.Error("Load config failed.", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Conf.Mail = conf.Mail
 | 
			
		||||
	Conf.Slack = conf.Slack
 | 
			
		||||
 | 
			
		||||
	d := conf.Default
 | 
			
		||||
	Conf.Default = d
 | 
			
		||||
	servers := make(map[string]ServerInfo)
 | 
			
		||||
 | 
			
		||||
	i := 0
 | 
			
		||||
	for name, v := range conf.Servers {
 | 
			
		||||
		s := ServerInfo{ServerName: name}
 | 
			
		||||
		s.User = v.User
 | 
			
		||||
		if s.User == "" {
 | 
			
		||||
			s.User = d.User
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.Password = v.Password
 | 
			
		||||
		if s.Password == "" {
 | 
			
		||||
			s.Password = d.Password
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.Host = v.Host
 | 
			
		||||
 | 
			
		||||
		s.Port = v.Port
 | 
			
		||||
		if s.Port == "" {
 | 
			
		||||
			s.Port = d.Port
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.KeyPath = v.KeyPath
 | 
			
		||||
		if s.KeyPath == "" {
 | 
			
		||||
			s.KeyPath = d.KeyPath
 | 
			
		||||
		}
 | 
			
		||||
		if s.KeyPath != "" {
 | 
			
		||||
			if _, err := os.Stat(s.KeyPath); err != nil {
 | 
			
		||||
				return fmt.Errorf(
 | 
			
		||||
					"config.toml is invalid. keypath: %s not exists", s.KeyPath)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.KeyPassword = v.KeyPassword
 | 
			
		||||
		if s.KeyPassword == "" {
 | 
			
		||||
			s.KeyPassword = d.KeyPassword
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.CpeNames = v.CpeNames
 | 
			
		||||
		if len(s.CpeNames) == 0 {
 | 
			
		||||
			s.CpeNames = d.CpeNames
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.LogMsgAnsiColor = Colors[i%len(conf.Servers)]
 | 
			
		||||
		i++
 | 
			
		||||
 | 
			
		||||
		servers[name] = s
 | 
			
		||||
	}
 | 
			
		||||
	log.Debug("Config loaded.")
 | 
			
		||||
	log.Debugf("%s", pp.Sprintf("%v", servers))
 | 
			
		||||
	Conf.Servers = servers
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										240
									
								
								cveapi/cve_client.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								cveapi/cve_client.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,240 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package cveapi
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/cenkalti/backoff"
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	cve "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// CveClient is api client of CVE disctionary service.
 | 
			
		||||
var CveClient cvedictClient
 | 
			
		||||
 | 
			
		||||
type cvedictClient struct {
 | 
			
		||||
	//  httpProxy string
 | 
			
		||||
	baseURL string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api *cvedictClient) initialize() {
 | 
			
		||||
	api.baseURL = config.Conf.CveDictionaryURL
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) CheckHealth() (ok bool, err error) {
 | 
			
		||||
	api.initialize()
 | 
			
		||||
	url := fmt.Sprintf("%s/health", api.baseURL)
 | 
			
		||||
	var errs []error
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	resp, _, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
	//  resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End()
 | 
			
		||||
	if len(errs) > 0 || resp.StatusCode != 200 {
 | 
			
		||||
		return false, fmt.Errorf("Failed to request to CVE server. url: %s, errs: %v",
 | 
			
		||||
			url,
 | 
			
		||||
			errs,
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type response struct {
 | 
			
		||||
	Key       string
 | 
			
		||||
	CveDetail cve.CveDetail
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails cve.CveDetails, err error) {
 | 
			
		||||
	api.baseURL = config.Conf.CveDictionaryURL
 | 
			
		||||
	reqChan := make(chan string, len(cveIDs))
 | 
			
		||||
	resChan := make(chan response, len(cveIDs))
 | 
			
		||||
	errChan := make(chan error, len(cveIDs))
 | 
			
		||||
	defer close(reqChan)
 | 
			
		||||
	defer close(resChan)
 | 
			
		||||
	defer close(errChan)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		for _, cveID := range cveIDs {
 | 
			
		||||
			reqChan <- cveID
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	concurrency := 10
 | 
			
		||||
	tasks := util.GenWorkers(concurrency)
 | 
			
		||||
	for range cveIDs {
 | 
			
		||||
		tasks <- func() {
 | 
			
		||||
			select {
 | 
			
		||||
			case cveID := <-reqChan:
 | 
			
		||||
				url, err := util.URLPathJoin(api.baseURL, "cves", cveID)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					errChan <- err
 | 
			
		||||
				} else {
 | 
			
		||||
					log.Debugf("HTTP Request to %s", url)
 | 
			
		||||
					api.httpGet(cveID, url, resChan, errChan)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	timeout := time.After(2 * 60 * time.Second)
 | 
			
		||||
	var errs []error
 | 
			
		||||
	for range cveIDs {
 | 
			
		||||
		select {
 | 
			
		||||
		case res := <-resChan:
 | 
			
		||||
			if len(res.CveDetail.CveID) == 0 {
 | 
			
		||||
				cveDetails = append(cveDetails, cve.CveDetail{
 | 
			
		||||
					CveID: res.Key,
 | 
			
		||||
				})
 | 
			
		||||
			} else {
 | 
			
		||||
				cveDetails = append(cveDetails, res.CveDetail)
 | 
			
		||||
			}
 | 
			
		||||
		case err := <-errChan:
 | 
			
		||||
			errs = append(errs, err)
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			return []cve.CveDetail{}, fmt.Errorf("Timeout Fetching CVE")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(errs) != 0 {
 | 
			
		||||
		return []cve.CveDetail{},
 | 
			
		||||
			fmt.Errorf("Failed to fetch CVE. err: %v", errs)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// order by CVE ID desc
 | 
			
		||||
	sort.Sort(cveDetails)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) httpGet(key, url string, resChan chan<- response, errChan chan<- error) {
 | 
			
		||||
 | 
			
		||||
	var body string
 | 
			
		||||
	var errs []error
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	f := func() (err error) {
 | 
			
		||||
		resp, body, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
		if len(errs) > 0 || resp.StatusCode != 200 {
 | 
			
		||||
			errChan <- fmt.Errorf("HTTP error. errs: %v, url: %s", errs, url)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	notify := func(err error, t time.Duration) {
 | 
			
		||||
		log.Warnf("Failed to get. retrying in %s seconds. err: %s", t, err)
 | 
			
		||||
	}
 | 
			
		||||
	err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errChan <- fmt.Errorf("HTTP Error %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	cveDetail := cve.CveDetail{}
 | 
			
		||||
	if err := json.Unmarshal([]byte(body), &cveDetail); err != nil {
 | 
			
		||||
		errChan <- fmt.Errorf("Failed to Unmarshall. body: %s, err: %s", body, err)
 | 
			
		||||
	}
 | 
			
		||||
	resChan <- response{
 | 
			
		||||
		key,
 | 
			
		||||
		cveDetail,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//  func (api cvedictClient) httpGet(key, url string, query map[string]string, resChan chan<- response, errChan chan<- error) {
 | 
			
		||||
 | 
			
		||||
//      var body string
 | 
			
		||||
//      var errs []error
 | 
			
		||||
//      var resp *http.Response
 | 
			
		||||
//      f := func() (err error) {
 | 
			
		||||
//          req := gorequest.New().SetDebug(true).Proxy(api.httpProxy).Get(url)
 | 
			
		||||
//          for key := range query {
 | 
			
		||||
//              req = req.Query(fmt.Sprintf("%s=%s", key, query[key])).Set("Content-Type", "application/x-www-form-urlencoded")
 | 
			
		||||
//          }
 | 
			
		||||
//          pp.Println(req)
 | 
			
		||||
//          resp, body, errs = req.End()
 | 
			
		||||
//          if len(errs) > 0 || resp.StatusCode != 200 {
 | 
			
		||||
//              errChan <- fmt.Errorf("HTTP error. errs: %v, url: %s", errs, url)
 | 
			
		||||
//          }
 | 
			
		||||
//          return nil
 | 
			
		||||
//      }
 | 
			
		||||
//      notify := func(err error, t time.Duration) {
 | 
			
		||||
//          log.Warnf("Failed to get. retrying in %s seconds. err: %s", t, err)
 | 
			
		||||
//      }
 | 
			
		||||
//      err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
 | 
			
		||||
//      if err != nil {
 | 
			
		||||
//          errChan <- fmt.Errorf("HTTP Error %s", err)
 | 
			
		||||
//      }
 | 
			
		||||
//      //  resChan <- body
 | 
			
		||||
//      cveDetail := cve.CveDetail{}
 | 
			
		||||
//      if err := json.Unmarshal([]byte(body), &cveDetail); err != nil {
 | 
			
		||||
//          errChan <- fmt.Errorf("Failed to Unmarshall. body: %s, err: %s", body, err)
 | 
			
		||||
//      }
 | 
			
		||||
//      resChan <- response{
 | 
			
		||||
//          key,
 | 
			
		||||
//          cveDetail,
 | 
			
		||||
//      }
 | 
			
		||||
//  }
 | 
			
		||||
 | 
			
		||||
type responseGetCveDetailByCpeName struct {
 | 
			
		||||
	CpeName    string
 | 
			
		||||
	CveDetails []cve.CveDetail
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) FetchCveDetailsByCpeName(cpeName string) ([]cve.CveDetail, error) {
 | 
			
		||||
	api.baseURL = config.Conf.CveDictionaryURL
 | 
			
		||||
 | 
			
		||||
	url, err := util.URLPathJoin(api.baseURL, "cpes")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return []cve.CveDetail{}, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	query := map[string]string{"name": cpeName}
 | 
			
		||||
	log.Debugf("HTTP Request to %s, query: %#v", url, query)
 | 
			
		||||
	return api.httpPost(cpeName, url, query)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) httpPost(key, url string, query map[string]string) ([]cve.CveDetail, error) {
 | 
			
		||||
	var body string
 | 
			
		||||
	var errs []error
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	f := func() (err error) {
 | 
			
		||||
		req := gorequest.New().SetDebug(config.Conf.Debug).Post(url)
 | 
			
		||||
		for key := range query {
 | 
			
		||||
			req = req.Send(fmt.Sprintf("%s=%s", key, query[key])).Type("json")
 | 
			
		||||
		}
 | 
			
		||||
		resp, body, errs = req.End()
 | 
			
		||||
		if len(errs) > 0 || resp.StatusCode != 200 {
 | 
			
		||||
			return fmt.Errorf("HTTP error. errs: %v, url: %s", errs, url)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	notify := func(err error, t time.Duration) {
 | 
			
		||||
		log.Warnf("Failed to get. retrying in %s seconds. err: %s", t, err)
 | 
			
		||||
	}
 | 
			
		||||
	err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return []cve.CveDetail{}, fmt.Errorf("HTTP Error %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cveDetails := []cve.CveDetail{}
 | 
			
		||||
	if err := json.Unmarshal([]byte(body), &cveDetails); err != nil {
 | 
			
		||||
		return []cve.CveDetail{},
 | 
			
		||||
			fmt.Errorf("Failed to Unmarshall. body: %s, err: %s", body, err)
 | 
			
		||||
	}
 | 
			
		||||
	return cveDetails, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										272
									
								
								db/db.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										272
									
								
								db/db.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,272 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package db
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	m "github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/jinzhu/gorm"
 | 
			
		||||
	cvedb "github.com/kotakanbe/go-cve-dictionary/db"
 | 
			
		||||
	cve "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var db *gorm.DB
 | 
			
		||||
 | 
			
		||||
// OpenDB opens Database
 | 
			
		||||
func OpenDB() (err error) {
 | 
			
		||||
	db, err = gorm.Open("sqlite3", config.Conf.DBPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		err = fmt.Errorf("Failed to open DB. datafile: %s, err: %s", config.Conf.DBPath, err)
 | 
			
		||||
		return
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	db.LogMode(config.Conf.DebugSQL)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MigrateDB migrates Database
 | 
			
		||||
func MigrateDB() error {
 | 
			
		||||
	if err := db.AutoMigrate(
 | 
			
		||||
		&m.ScanHistory{},
 | 
			
		||||
		&m.ScanResult{},
 | 
			
		||||
		//  &m.NWLink{},
 | 
			
		||||
		&m.CveInfo{},
 | 
			
		||||
		&m.CpeName{},
 | 
			
		||||
		&m.PackageInfo{},
 | 
			
		||||
		&m.DistroAdvisory{},
 | 
			
		||||
		&cve.CveDetail{},
 | 
			
		||||
		&cve.Jvn{},
 | 
			
		||||
		&cve.Nvd{},
 | 
			
		||||
		&cve.Reference{},
 | 
			
		||||
		&cve.Cpe{},
 | 
			
		||||
	).Error; err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to migrate. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	errMsg := "Failed to create index. err: %s"
 | 
			
		||||
	//  if err := db.Model(&m.NWLink{}).
 | 
			
		||||
	//      AddIndex("idx_n_w_links_scan_result_id", "scan_result_id").Error; err != nil {
 | 
			
		||||
	//      return fmt.Errorf(errMsg, err)
 | 
			
		||||
	//  }
 | 
			
		||||
	if err := db.Model(&m.CveInfo{}).
 | 
			
		||||
		AddIndex("idx_cve_infos_scan_result_id", "scan_result_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&m.CpeName{}).
 | 
			
		||||
		AddIndex("idx_cpe_names_cve_info_id", "cve_info_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&m.PackageInfo{}).
 | 
			
		||||
		AddIndex("idx_package_infos_cve_info_id", "cve_info_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&m.DistroAdvisory{}).
 | 
			
		||||
		//TODO check table name
 | 
			
		||||
		AddIndex("idx_distro_advisories_cve_info_id", "cve_info_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.CveDetail{}).
 | 
			
		||||
		AddIndex("idx_cve_detail_cve_info_id", "cve_info_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.CveDetail{}).
 | 
			
		||||
		AddIndex("idx_cve_detail_cveid", "cve_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Nvd{}).
 | 
			
		||||
		AddIndex("idx_nvds_cve_detail_id", "cve_detail_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Jvn{}).
 | 
			
		||||
		AddIndex("idx_jvns_cve_detail_id", "cve_detail_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Cpe{}).
 | 
			
		||||
		AddIndex("idx_cpes_jvn_id", "jvn_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Reference{}).
 | 
			
		||||
		AddIndex("idx_references_jvn_id", "jvn_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Cpe{}).
 | 
			
		||||
		AddIndex("idx_cpes_nvd_id", "nvd_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Model(&cve.Reference{}).
 | 
			
		||||
		AddIndex("idx_references_nvd_id", "nvd_id").Error; err != nil {
 | 
			
		||||
		return fmt.Errorf(errMsg, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Insert inserts scan results into DB
 | 
			
		||||
func Insert(results []m.ScanResult) error {
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		r.KnownCves = resetGormIDs(r.KnownCves)
 | 
			
		||||
		r.UnknownCves = resetGormIDs(r.UnknownCves)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	history := m.ScanHistory{
 | 
			
		||||
		ScanResults: results,
 | 
			
		||||
		ScannedAt:   time.Now(),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	db = db.Set("gorm:save_associations", false)
 | 
			
		||||
	if err := db.Create(&history).Error; err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	for _, scanResult := range history.ScanResults {
 | 
			
		||||
		scanResult.ScanHistoryID = history.ID
 | 
			
		||||
		if err := db.Create(&scanResult).Error; err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if err := insertCveInfos(scanResult.ID, scanResult.KnownCves); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if err := insertCveInfos(scanResult.ID, scanResult.UnknownCves); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func insertCveInfos(scanResultID uint, infos []m.CveInfo) error {
 | 
			
		||||
	for _, cveInfo := range infos {
 | 
			
		||||
		cveInfo.ScanResultID = scanResultID
 | 
			
		||||
		if err := db.Create(&cveInfo).Error; err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, pack := range cveInfo.Packages {
 | 
			
		||||
			pack.CveInfoID = cveInfo.ID
 | 
			
		||||
			if err := db.Create(&pack).Error; err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, distroAdvisory := range cveInfo.DistroAdvisories {
 | 
			
		||||
			distroAdvisory.CveInfoID = cveInfo.ID
 | 
			
		||||
			if err := db.Create(&distroAdvisory).Error; err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, cpeName := range cveInfo.CpeNames {
 | 
			
		||||
			cpeName.CveInfoID = cveInfo.ID
 | 
			
		||||
			if err := db.Create(&cpeName).Error; err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		db = db.Set("gorm:save_associations", true)
 | 
			
		||||
		cveDetail := cveInfo.CveDetail
 | 
			
		||||
		cveDetail.CveInfoID = cveInfo.ID
 | 
			
		||||
		if err := db.Create(&cveDetail).Error; err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		db = db.Set("gorm:save_associations", false)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func resetGormIDs(infos []m.CveInfo) []m.CveInfo {
 | 
			
		||||
	for i := range infos {
 | 
			
		||||
		infos[i].CveDetail.ID = 0
 | 
			
		||||
		// NVD
 | 
			
		||||
		infos[i].CveDetail.Nvd.ID = 0
 | 
			
		||||
		for j := range infos[i].CveDetail.Nvd.Cpes {
 | 
			
		||||
			infos[i].CveDetail.Nvd.Cpes[j].ID = 0
 | 
			
		||||
		}
 | 
			
		||||
		for j := range infos[i].CveDetail.Nvd.References {
 | 
			
		||||
			infos[i].CveDetail.Nvd.References[j].ID = 0
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// JVN
 | 
			
		||||
		infos[i].CveDetail.Jvn.ID = 0
 | 
			
		||||
		for j := range infos[i].CveDetail.Jvn.Cpes {
 | 
			
		||||
			infos[i].CveDetail.Jvn.Cpes[j].ID = 0
 | 
			
		||||
		}
 | 
			
		||||
		for j := range infos[i].CveDetail.Jvn.References {
 | 
			
		||||
			infos[i].CveDetail.Jvn.References[j].ID = 0
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//Packages
 | 
			
		||||
		for j := range infos[i].Packages {
 | 
			
		||||
			infos[i].Packages[j].ID = 0
 | 
			
		||||
			infos[i].Packages[j].CveInfoID = 0
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return infos
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SelectLatestScanHistory select latest scan history from DB
 | 
			
		||||
func SelectLatestScanHistory() (m.ScanHistory, error) {
 | 
			
		||||
	scanHistory := m.ScanHistory{}
 | 
			
		||||
	db.Order("scanned_at desc").First(&scanHistory)
 | 
			
		||||
 | 
			
		||||
	if scanHistory.ID == 0 {
 | 
			
		||||
		return m.ScanHistory{}, fmt.Errorf("No scanHistory records.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	results := []m.ScanResult{}
 | 
			
		||||
	db.Model(&scanHistory).Related(&results, "ScanResults")
 | 
			
		||||
	scanHistory.ScanResults = results
 | 
			
		||||
 | 
			
		||||
	for i, r := range results {
 | 
			
		||||
		//  nw := []m.NWLink{}
 | 
			
		||||
		//  db.Model(&r).Related(&nw, "NWLinks")
 | 
			
		||||
		//  scanHistory.ScanResults[i].NWLinks = nw
 | 
			
		||||
 | 
			
		||||
		knownCves := selectCveInfos(&r, "KnownCves")
 | 
			
		||||
		sort.Sort(m.CveInfos(knownCves))
 | 
			
		||||
		scanHistory.ScanResults[i].KnownCves = knownCves
 | 
			
		||||
	}
 | 
			
		||||
	return scanHistory, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func selectCveInfos(result *m.ScanResult, fieldName string) []m.CveInfo {
 | 
			
		||||
	cveInfos := []m.CveInfo{}
 | 
			
		||||
	db.Model(&result).Related(&cveInfos, fieldName)
 | 
			
		||||
 | 
			
		||||
	for i, cveInfo := range cveInfos {
 | 
			
		||||
		cveDetail := cve.CveDetail{}
 | 
			
		||||
		db.Model(&cveInfo).Related(&cveDetail, "CveDetail")
 | 
			
		||||
		id := cveDetail.CveID
 | 
			
		||||
		filledCveDetail := cvedb.Get(id, db)
 | 
			
		||||
		cveInfos[i].CveDetail = filledCveDetail
 | 
			
		||||
 | 
			
		||||
		packs := []m.PackageInfo{}
 | 
			
		||||
		db.Model(&cveInfo).Related(&packs, "Packages")
 | 
			
		||||
		cveInfos[i].Packages = packs
 | 
			
		||||
 | 
			
		||||
		advisories := []m.DistroAdvisory{}
 | 
			
		||||
		db.Model(&cveInfo).Related(&advisories, "DistroAdvisories")
 | 
			
		||||
		cveInfos[i].DistroAdvisories = advisories
 | 
			
		||||
 | 
			
		||||
		names := []m.CpeName{}
 | 
			
		||||
		db.Model(&cveInfo).Related(&names, "CpeNames")
 | 
			
		||||
		cveInfos[i].CpeNames = names
 | 
			
		||||
	}
 | 
			
		||||
	return cveInfos
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"flag"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/commands"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
 | 
			
		||||
	_ "github.com/mattn/go-sqlite3"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	subcommands.Register(subcommands.HelpCommand(), "")
 | 
			
		||||
	subcommands.Register(subcommands.FlagsCommand(), "")
 | 
			
		||||
	subcommands.Register(subcommands.CommandsCommand(), "")
 | 
			
		||||
	subcommands.Register(&commands.DiscoverCmd{}, "discover")
 | 
			
		||||
	subcommands.Register(&commands.TuiCmd{}, "tui")
 | 
			
		||||
	subcommands.Register(&commands.ScanCmd{}, "scan")
 | 
			
		||||
	subcommands.Register(&commands.PrepareCmd{}, "prepare")
 | 
			
		||||
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	os.Exit(int(subcommands.Execute(ctx)))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										244
									
								
								models/models.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										244
									
								
								models/models.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,244 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/jinzhu/gorm"
 | 
			
		||||
	cve "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ScanHistory is the history of Scanning.
 | 
			
		||||
type ScanHistory struct {
 | 
			
		||||
	gorm.Model
 | 
			
		||||
	ScanResults []ScanResult
 | 
			
		||||
	ScannedAt   time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ScanResults is slice of ScanResult.
 | 
			
		||||
type ScanResults []ScanResult
 | 
			
		||||
 | 
			
		||||
// FilterByCvssOver is filter function.
 | 
			
		||||
func (results ScanResults) FilterByCvssOver() (filtered ScanResults) {
 | 
			
		||||
	for _, result := range results {
 | 
			
		||||
		cveInfos := []CveInfo{}
 | 
			
		||||
		for _, cveInfo := range result.KnownCves {
 | 
			
		||||
			if config.Conf.CvssScoreOver < cveInfo.CveDetail.CvssScore(config.Conf.Lang) {
 | 
			
		||||
				cveInfos = append(cveInfos, cveInfo)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		result.KnownCves = cveInfos
 | 
			
		||||
		filtered = append(filtered, result)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ScanResult has the result of scanned CVE information.
 | 
			
		||||
type ScanResult struct {
 | 
			
		||||
	gorm.Model
 | 
			
		||||
	ScanHistoryID uint
 | 
			
		||||
 | 
			
		||||
	ServerName string // TOML Section key
 | 
			
		||||
	//  Hostname    string
 | 
			
		||||
	Family  string
 | 
			
		||||
	Release string
 | 
			
		||||
	//  Fqdn        string
 | 
			
		||||
	//  NWLinks     []NWLink
 | 
			
		||||
	KnownCves   []CveInfo
 | 
			
		||||
	UnknownCves []CveInfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveSummary summarize the number of CVEs group by CVSSv2 Severity
 | 
			
		||||
func (r ScanResult) CveSummary() string {
 | 
			
		||||
	var high, middle, low, unknown int
 | 
			
		||||
	cves := append(r.KnownCves, r.UnknownCves...)
 | 
			
		||||
	for _, cveInfo := range cves {
 | 
			
		||||
		score := cveInfo.CveDetail.CvssScore(config.Conf.Lang)
 | 
			
		||||
		switch {
 | 
			
		||||
		case 7.0 < score:
 | 
			
		||||
			high++
 | 
			
		||||
		case 4.0 < score:
 | 
			
		||||
			middle++
 | 
			
		||||
		case 0 < score:
 | 
			
		||||
			low++
 | 
			
		||||
		default:
 | 
			
		||||
			unknown++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("Total: %d (High:%d Middle:%d Low:%d ?:%d)",
 | 
			
		||||
		high+middle+low+unknown,
 | 
			
		||||
		high, middle, low, unknown,
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NWLink has network link information.
 | 
			
		||||
type NWLink struct {
 | 
			
		||||
	gorm.Model
 | 
			
		||||
	ScanResultID uint
 | 
			
		||||
 | 
			
		||||
	IPAddress string
 | 
			
		||||
	Netmask   string
 | 
			
		||||
	DevName   string
 | 
			
		||||
	LinkState string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveInfos is for sorting
 | 
			
		||||
type CveInfos []CveInfo
 | 
			
		||||
 | 
			
		||||
func (c CveInfos) Len() int {
 | 
			
		||||
	return len(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c CveInfos) Swap(i, j int) {
 | 
			
		||||
	c[i], c[j] = c[j], c[i]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c CveInfos) Less(i, j int) bool {
 | 
			
		||||
	lang := config.Conf.Lang
 | 
			
		||||
	return c[i].CveDetail.CvssScore(lang) > c[j].CveDetail.CvssScore(lang)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveInfo has Cve Information.
 | 
			
		||||
type CveInfo struct {
 | 
			
		||||
	gorm.Model
 | 
			
		||||
	ScanResultID uint
 | 
			
		||||
 | 
			
		||||
	CveDetail        cve.CveDetail
 | 
			
		||||
	Packages         []PackageInfo
 | 
			
		||||
	DistroAdvisories []DistroAdvisory
 | 
			
		||||
	CpeNames         []CpeName
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CpeName has CPE name
 | 
			
		||||
type CpeName struct {
 | 
			
		||||
	gorm.Model
 | 
			
		||||
	CveInfoID uint
 | 
			
		||||
 | 
			
		||||
	Name string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PackageInfoList is slice of PackageInfo
 | 
			
		||||
type PackageInfoList []PackageInfo
 | 
			
		||||
 | 
			
		||||
// Exists returns true if exists the name
 | 
			
		||||
func (ps PackageInfoList) Exists(name string) bool {
 | 
			
		||||
	for _, p := range ps {
 | 
			
		||||
		if p.Name == name {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UniqByName be uniq by name.
 | 
			
		||||
func (ps PackageInfoList) UniqByName() (distincted PackageInfoList) {
 | 
			
		||||
	set := make(map[string]PackageInfo)
 | 
			
		||||
	for _, p := range ps {
 | 
			
		||||
		set[p.Name] = p
 | 
			
		||||
	}
 | 
			
		||||
	//sort by key
 | 
			
		||||
	keys := []string{}
 | 
			
		||||
	for key := range set {
 | 
			
		||||
		keys = append(keys, key)
 | 
			
		||||
	}
 | 
			
		||||
	sort.Strings(keys)
 | 
			
		||||
	for _, key := range keys {
 | 
			
		||||
		distincted = append(distincted, set[key])
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindByName search PackageInfo by name
 | 
			
		||||
func (ps PackageInfoList) FindByName(name string) (result PackageInfo, found bool) {
 | 
			
		||||
	for _, p := range ps {
 | 
			
		||||
		if p.Name == name {
 | 
			
		||||
			return p, true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return PackageInfo{}, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Find search PackageInfo by name-version-release
 | 
			
		||||
//  func (ps PackageInfoList) find(nameVersionRelease string) (PackageInfo, bool) {
 | 
			
		||||
//      for _, p := range ps {
 | 
			
		||||
//          joined := p.Name
 | 
			
		||||
//          if 0 < len(p.Version) {
 | 
			
		||||
//              joined = fmt.Sprintf("%s-%s", joined, p.Version)
 | 
			
		||||
//          }
 | 
			
		||||
//          if 0 < len(p.Release) {
 | 
			
		||||
//              joined = fmt.Sprintf("%s-%s", joined, p.Release)
 | 
			
		||||
//          }
 | 
			
		||||
//          if joined == nameVersionRelease {
 | 
			
		||||
//              return p, true
 | 
			
		||||
//          }
 | 
			
		||||
//      }
 | 
			
		||||
//      return PackageInfo{}, false
 | 
			
		||||
//  }
 | 
			
		||||
 | 
			
		||||
// PackageInfo has installed packages.
 | 
			
		||||
type PackageInfo struct {
 | 
			
		||||
	gorm.Model
 | 
			
		||||
	CveInfoID uint
 | 
			
		||||
 | 
			
		||||
	Name    string
 | 
			
		||||
	Version string
 | 
			
		||||
	Release string
 | 
			
		||||
 | 
			
		||||
	NewVersion string
 | 
			
		||||
	NewRelease string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToStringCurrentVersion returns package name-version-release
 | 
			
		||||
func (p PackageInfo) ToStringCurrentVersion() string {
 | 
			
		||||
	str := p.Name
 | 
			
		||||
	if 0 < len(p.Version) {
 | 
			
		||||
		str = fmt.Sprintf("%s-%s", str, p.Version)
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(p.Release) {
 | 
			
		||||
		str = fmt.Sprintf("%s-%s", str, p.Release)
 | 
			
		||||
	}
 | 
			
		||||
	return str
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToStringNewVersion returns package name-version-release
 | 
			
		||||
func (p PackageInfo) ToStringNewVersion() string {
 | 
			
		||||
	str := p.Name
 | 
			
		||||
	if 0 < len(p.NewVersion) {
 | 
			
		||||
		str = fmt.Sprintf("%s-%s", str, p.NewVersion)
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(p.NewRelease) {
 | 
			
		||||
		str = fmt.Sprintf("%s-%s", str, p.NewRelease)
 | 
			
		||||
	}
 | 
			
		||||
	return str
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DistroAdvisory has Amazon Linux AMI Security Advisory information.
 | 
			
		||||
//TODO Rename to DistroAdvisory
 | 
			
		||||
type DistroAdvisory struct {
 | 
			
		||||
	gorm.Model
 | 
			
		||||
	CveInfoID uint
 | 
			
		||||
 | 
			
		||||
	AdvisoryID string
 | 
			
		||||
	Severity   string
 | 
			
		||||
	Issued     time.Time
 | 
			
		||||
	Updated    time.Time
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										54
									
								
								models/models_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								models/models_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import "testing"
 | 
			
		||||
 | 
			
		||||
func TestPackageInfosUniqByName(t *testing.T) {
 | 
			
		||||
	var test = struct {
 | 
			
		||||
		in  PackageInfoList
 | 
			
		||||
		out PackageInfoList
 | 
			
		||||
	}{
 | 
			
		||||
		PackageInfoList{
 | 
			
		||||
			{
 | 
			
		||||
				Name: "hoge",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name: "fuga",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name: "hoge",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		PackageInfoList{
 | 
			
		||||
			{
 | 
			
		||||
				Name: "hoge",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name: "fuga",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	actual := test.in.UniqByName()
 | 
			
		||||
	for i, ePack := range test.out {
 | 
			
		||||
		if actual[i].Name == ePack.Name {
 | 
			
		||||
			t.Errorf("expected %#v, actual %#v", ePack.Name, actual[i].Name)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								report/json.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								report/json.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// JSONWriter writes report as JSON format
 | 
			
		||||
type JSONWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w JSONWriter) Write(scanResults []models.ScanResult) (err error) {
 | 
			
		||||
	var j []byte
 | 
			
		||||
	if j, err = json.MarshalIndent(scanResults, "", "  "); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println(string(j))
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										51
									
								
								report/logrus.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								report/logrus.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	formatter "github.com/kotakanbe/logrus-prefixed-formatter"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// LogrusWriter write to logfile
 | 
			
		||||
type LogrusWriter struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w LogrusWriter) Write(scanResults []models.ScanResult) error {
 | 
			
		||||
	path := "/var/log/vuls/report.log"
 | 
			
		||||
	f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	log := logrus.New()
 | 
			
		||||
	log.Formatter = &formatter.TextFormatter{}
 | 
			
		||||
	log.Out = f
 | 
			
		||||
	log.Level = logrus.InfoLevel
 | 
			
		||||
 | 
			
		||||
	for _, s := range scanResults {
 | 
			
		||||
		text, err := toPlainText(s)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		log.Infof(text)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										70
									
								
								report/mail.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								report/mail.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"gopkg.in/gomail.v2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// MailWriter send mail
 | 
			
		||||
type MailWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w MailWriter) Write(scanResults []models.ScanResult) (err error) {
 | 
			
		||||
	conf := config.Conf
 | 
			
		||||
	for _, s := range scanResults {
 | 
			
		||||
		m := gomail.NewMessage()
 | 
			
		||||
		m.SetHeader("From", conf.Mail.From)
 | 
			
		||||
		m.SetHeader("To", conf.Mail.To...)
 | 
			
		||||
		m.SetHeader("Cc", conf.Mail.Cc...)
 | 
			
		||||
 | 
			
		||||
		subject := fmt.Sprintf("%s%s %s",
 | 
			
		||||
			conf.Mail.SubjectPrefix,
 | 
			
		||||
			s.ServerName,
 | 
			
		||||
			s.CveSummary(),
 | 
			
		||||
		)
 | 
			
		||||
		m.SetHeader("Subject", subject)
 | 
			
		||||
 | 
			
		||||
		var body string
 | 
			
		||||
		if body, err = toPlainText(s); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		m.SetBody("text/plain", body)
 | 
			
		||||
		port, _ := strconv.Atoi(conf.Mail.SMTPPort)
 | 
			
		||||
		d := gomail.NewPlainDialer(
 | 
			
		||||
			conf.Mail.SMTPAddr,
 | 
			
		||||
			port,
 | 
			
		||||
			conf.Mail.User,
 | 
			
		||||
			conf.Mail.Password,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		d.TLSConfig = &tls.Config{
 | 
			
		||||
			InsecureSkipVerify: true,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := d.DialAndSend(m); err != nil {
 | 
			
		||||
			panic(err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										236
									
								
								report/slack.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										236
									
								
								report/slack.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,236 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/cenkalti/backoff"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type field struct {
 | 
			
		||||
	Title string `json:"title"`
 | 
			
		||||
	Value string `json:"value"`
 | 
			
		||||
	Short bool   `json:"short"`
 | 
			
		||||
}
 | 
			
		||||
type attachment struct {
 | 
			
		||||
	Title     string   `json:"title"`
 | 
			
		||||
	TitleLink string   `json:"title_link"`
 | 
			
		||||
	Fallback  string   `json:"fallback"`
 | 
			
		||||
	Text      string   `json:"text"`
 | 
			
		||||
	Pretext   string   `json:"pretext"`
 | 
			
		||||
	Color     string   `json:"color"`
 | 
			
		||||
	Fields    []*field `json:"fields"`
 | 
			
		||||
	MrkdwnIn  []string `json:"mrkdwn_in"`
 | 
			
		||||
}
 | 
			
		||||
type message struct {
 | 
			
		||||
	Text        string        `json:"text"`
 | 
			
		||||
	Username    string        `json:"username"`
 | 
			
		||||
	IconEmoji   string        `json:"icon_emoji"`
 | 
			
		||||
	Channel     string        `json:"channel"`
 | 
			
		||||
	Attachments []*attachment `json:"attachments"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SlackWriter send report to slack
 | 
			
		||||
type SlackWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w SlackWriter) Write(scanResults []models.ScanResult) error {
 | 
			
		||||
	conf := config.Conf.Slack
 | 
			
		||||
	for _, s := range scanResults {
 | 
			
		||||
 | 
			
		||||
		channel := conf.Channel
 | 
			
		||||
		if channel == "${servername}" {
 | 
			
		||||
			channel = fmt.Sprintf("#%s", s.ServerName)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		msg := message{
 | 
			
		||||
			Text:        msgText(s),
 | 
			
		||||
			Username:    conf.AuthUser,
 | 
			
		||||
			IconEmoji:   conf.IconEmoji,
 | 
			
		||||
			Channel:     channel,
 | 
			
		||||
			Attachments: toSlackAttachments(s),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		bytes, _ := json.Marshal(msg)
 | 
			
		||||
		jsonBody := string(bytes)
 | 
			
		||||
		f := func() (err error) {
 | 
			
		||||
			resp, body, errs := gorequest.New().Proxy(config.Conf.HTTPProxy).Post(conf.HookURL).
 | 
			
		||||
				Send(string(jsonBody)).End()
 | 
			
		||||
			if resp.StatusCode != 200 {
 | 
			
		||||
				log.Errorf("Resonse body: %s", body)
 | 
			
		||||
				if len(errs) > 0 {
 | 
			
		||||
					return errs[0]
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		notify := func(err error, t time.Duration) {
 | 
			
		||||
			log.Warn("Retrying in ", t)
 | 
			
		||||
		}
 | 
			
		||||
		if err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify); err != nil {
 | 
			
		||||
			return fmt.Errorf("HTTP Error: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func msgText(r models.ScanResult) string {
 | 
			
		||||
 | 
			
		||||
	notifyUsers := ""
 | 
			
		||||
	if 0 < len(r.KnownCves) || 0 < len(r.UnknownCves) {
 | 
			
		||||
		notifyUsers = getNotifyUsers(config.Conf.Slack.NotifyUsers)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hostinfo := fmt.Sprintf(
 | 
			
		||||
		"*%s* (%s %s)",
 | 
			
		||||
		r.ServerName,
 | 
			
		||||
		r.Family,
 | 
			
		||||
		r.Release,
 | 
			
		||||
	)
 | 
			
		||||
	return fmt.Sprintf("%s\n%s\n>%s", notifyUsers, hostinfo, r.CveSummary())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toSlackAttachments(scanResult models.ScanResult) (attaches []*attachment) {
 | 
			
		||||
 | 
			
		||||
	scanResult.KnownCves = append(scanResult.KnownCves, scanResult.UnknownCves...)
 | 
			
		||||
	for _, cveInfo := range scanResult.KnownCves {
 | 
			
		||||
		cveID := cveInfo.CveDetail.CveID
 | 
			
		||||
 | 
			
		||||
		curentPackages := []string{}
 | 
			
		||||
		for _, p := range cveInfo.Packages {
 | 
			
		||||
			curentPackages = append(curentPackages, p.ToStringCurrentVersion())
 | 
			
		||||
		}
 | 
			
		||||
		for _, cpename := range cveInfo.CpeNames {
 | 
			
		||||
			curentPackages = append(curentPackages, cpename.Name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		newPackages := []string{}
 | 
			
		||||
		for _, p := range cveInfo.Packages {
 | 
			
		||||
			newPackages = append(newPackages, p.ToStringNewVersion())
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		a := attachment{
 | 
			
		||||
			Title:     cveID,
 | 
			
		||||
			TitleLink: fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID),
 | 
			
		||||
			Text:      attachmentText(cveInfo, scanResult.Family),
 | 
			
		||||
			MrkdwnIn:  []string{"text", "pretext"},
 | 
			
		||||
			Fields: []*field{
 | 
			
		||||
				{
 | 
			
		||||
					//  Title: "Current Package/CPE",
 | 
			
		||||
					Title: "Installed",
 | 
			
		||||
					Value: strings.Join(curentPackages, "\n"),
 | 
			
		||||
					Short: true,
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Title: "Candidate",
 | 
			
		||||
					Value: strings.Join(newPackages, "\n"),
 | 
			
		||||
					Short: true,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			Color: color(cveInfo.CveDetail.CvssScore(config.Conf.Lang)),
 | 
			
		||||
		}
 | 
			
		||||
		attaches = append(attaches, &a)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// https://api.slack.com/docs/attachments
 | 
			
		||||
func color(cvssScore float64) string {
 | 
			
		||||
	switch {
 | 
			
		||||
	case 7 <= cvssScore:
 | 
			
		||||
		return "danger"
 | 
			
		||||
	case 4 <= cvssScore && cvssScore < 7:
 | 
			
		||||
		return "warning"
 | 
			
		||||
	case cvssScore < 0:
 | 
			
		||||
		return "#C0C0C0"
 | 
			
		||||
	default:
 | 
			
		||||
		return "good"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func attachmentText(cveInfo models.CveInfo, osFamily string) string {
 | 
			
		||||
 | 
			
		||||
	linkText := links(cveInfo, osFamily)
 | 
			
		||||
 | 
			
		||||
	switch {
 | 
			
		||||
	case config.Conf.Lang == "ja" &&
 | 
			
		||||
		cveInfo.CveDetail.Jvn.ID != 0 &&
 | 
			
		||||
		0 < cveInfo.CveDetail.CvssScore("ja"):
 | 
			
		||||
 | 
			
		||||
		jvn := cveInfo.CveDetail.Jvn
 | 
			
		||||
		return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s",
 | 
			
		||||
			cveInfo.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
			jvn.Severity,
 | 
			
		||||
			fmt.Sprintf(cvssV2CalcURLTemplate, cveInfo.CveDetail.CveID, jvn.Vector),
 | 
			
		||||
			jvn.Vector,
 | 
			
		||||
			jvn.Title,
 | 
			
		||||
			linkText,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
	case 0 < cveInfo.CveDetail.CvssScore("en"):
 | 
			
		||||
		nvd := cveInfo.CveDetail.Nvd
 | 
			
		||||
		return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s",
 | 
			
		||||
			cveInfo.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
			nvd.Severity(),
 | 
			
		||||
			fmt.Sprintf(cvssV2CalcURLTemplate, cveInfo.CveDetail.CveID, nvd.CvssVector()),
 | 
			
		||||
			nvd.CvssVector(),
 | 
			
		||||
			nvd.Summary,
 | 
			
		||||
			linkText,
 | 
			
		||||
		)
 | 
			
		||||
	default:
 | 
			
		||||
		nvd := cveInfo.CveDetail.Nvd
 | 
			
		||||
		return fmt.Sprintf("?\n%s\n%s", nvd.Summary, linkText)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func links(cveInfo models.CveInfo, osFamily string) string {
 | 
			
		||||
	links := []string{}
 | 
			
		||||
	cveID := cveInfo.CveDetail.CveID
 | 
			
		||||
	if config.Conf.Lang == "ja" && 0 < len(cveInfo.CveDetail.Jvn.Link()) {
 | 
			
		||||
		jvn := fmt.Sprintf("<%s|JVN>", cveInfo.CveDetail.Jvn.Link())
 | 
			
		||||
		links = append(links, jvn)
 | 
			
		||||
	}
 | 
			
		||||
	links = append(links, fmt.Sprintf("<%s|CVEDetails>",
 | 
			
		||||
		fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID)))
 | 
			
		||||
	links = append(links, fmt.Sprintf("<%s|MITRE>",
 | 
			
		||||
		fmt.Sprintf("%s%s", mitreBaseURL, cveID)))
 | 
			
		||||
 | 
			
		||||
	dlinks := distroLinks(cveInfo, osFamily)
 | 
			
		||||
	for _, link := range dlinks {
 | 
			
		||||
		links = append(links,
 | 
			
		||||
			fmt.Sprintf("<%s|%s>", link.url, link.title))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return strings.Join(links, " / ")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// See testcase
 | 
			
		||||
func getNotifyUsers(notifyUsers []string) string {
 | 
			
		||||
	slackStyleTexts := []string{}
 | 
			
		||||
	for _, username := range notifyUsers {
 | 
			
		||||
		slackStyleTexts = append(slackStyleTexts, fmt.Sprintf("<%s>", username))
 | 
			
		||||
	}
 | 
			
		||||
	return strings.Join(slackStyleTexts, " ")
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								report/slack_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								report/slack_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import "testing"
 | 
			
		||||
 | 
			
		||||
func TestGetNotifyUsers(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       []string
 | 
			
		||||
		expected string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			[]string{"@user1", "@user2"},
 | 
			
		||||
			"<@user1> <@user2>",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := getNotifyUsers(tt.in)
 | 
			
		||||
		if tt.expected != actual {
 | 
			
		||||
			t.Errorf("expected %s, actual %s", tt.expected, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								report/stdout.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								report/stdout.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TextWriter write to stdout
 | 
			
		||||
type TextWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w TextWriter) Write(scanResults []models.ScanResult) error {
 | 
			
		||||
	for _, s := range scanResults {
 | 
			
		||||
		text, err := toPlainText(s)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Println(text)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										770
									
								
								report/tui.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										770
									
								
								report/tui.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,770 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"text/template"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/db"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"github.com/gosuri/uitable"
 | 
			
		||||
	"github.com/jroimartin/gocui"
 | 
			
		||||
	cve "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var scanHistory models.ScanHistory
 | 
			
		||||
var currentScanResult models.ScanResult
 | 
			
		||||
var currentCveInfo int
 | 
			
		||||
var currentDetailLimitY int
 | 
			
		||||
 | 
			
		||||
// RunTui execute main logic
 | 
			
		||||
func RunTui() subcommands.ExitStatus {
 | 
			
		||||
	var err error
 | 
			
		||||
	scanHistory, err = latestScanHistory()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	g := gocui.NewGui()
 | 
			
		||||
	if err := g.Init(); err != nil {
 | 
			
		||||
		log.Panicln(err)
 | 
			
		||||
	}
 | 
			
		||||
	defer g.Close()
 | 
			
		||||
 | 
			
		||||
	g.SetLayout(layout)
 | 
			
		||||
	if err := keybindings(g); err != nil {
 | 
			
		||||
		log.Panicln(err)
 | 
			
		||||
	}
 | 
			
		||||
	g.SelBgColor = gocui.ColorGreen
 | 
			
		||||
	g.SelFgColor = gocui.ColorBlack
 | 
			
		||||
	g.Cursor = true
 | 
			
		||||
 | 
			
		||||
	if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
 | 
			
		||||
		log.Panicln(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func latestScanHistory() (latest models.ScanHistory, err error) {
 | 
			
		||||
	if err := db.OpenDB(); err != nil {
 | 
			
		||||
		return latest, fmt.Errorf(
 | 
			
		||||
			"Failed to open DB. datafile: %s, err: %s", config.Conf.DBPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	latest, err = db.SelectLatestScanHistory()
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func keybindings(g *gocui.Gui) (err error) {
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
 | 
			
		||||
	// Move beetween views
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("side", gocui.KeyTab, gocui.ModNone, nextView))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlH, gocui.ModNone, previousView))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlL, gocui.ModNone, nextView))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("side", gocui.KeyArrowRight, gocui.ModAlt, nextView))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("side", gocui.KeyArrowDown, gocui.ModNone, cursorDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlJ, gocui.ModNone, cursorDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("side", gocui.KeyArrowUp, gocui.ModNone, cursorUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlK, gocui.ModNone, cursorUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlD, gocui.ModNone, cursorPageDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlU, gocui.ModNone, cursorPageUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("side", gocui.KeySpace, gocui.ModNone, cursorPageDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("side", gocui.KeyBackspace, gocui.ModNone, cursorPageUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("side", gocui.KeyBackspace2, gocui.ModNone, cursorPageUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlN, gocui.ModNone, cursorDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlP, gocui.ModNone, cursorUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("side", gocui.KeyEnter, gocui.ModNone, nextView))
 | 
			
		||||
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("msg", gocui.KeyEnter, gocui.ModNone, delMsg))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("side", gocui.KeyEnter, gocui.ModNone, showMsg))
 | 
			
		||||
 | 
			
		||||
	// summary
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyTab, gocui.ModNone, nextView))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlQ, gocui.ModNone, previousView))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlH, gocui.ModNone, previousView))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlL, gocui.ModNone, nextView))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowLeft, gocui.ModAlt, previousView))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowDown, gocui.ModAlt, nextView))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowDown, gocui.ModNone, cursorDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowUp, gocui.ModNone, cursorUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlJ, gocui.ModNone, cursorDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlK, gocui.ModNone, cursorUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlD, gocui.ModNone, cursorPageDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlU, gocui.ModNone, cursorPageUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeySpace, gocui.ModNone, cursorPageDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyBackspace, gocui.ModNone, cursorPageUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyBackspace2, gocui.ModNone, cursorPageUp))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlM, gocui.ModNone, cursorMoveMiddle))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyEnter, gocui.ModNone, nextView))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlN, gocui.ModNone, nextSummary))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlP, gocui.ModNone, previousSummary))
 | 
			
		||||
 | 
			
		||||
	// detail
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyTab, gocui.ModNone, nextView))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlQ, gocui.ModNone, previousView))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlH, gocui.ModNone, nextView))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlL, gocui.ModNone, nextView))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowUp, gocui.ModAlt, previousView))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowLeft, gocui.ModAlt, nextView))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowDown, gocui.ModNone, cursorDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowUp, gocui.ModNone, cursorUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlJ, gocui.ModNone, cursorDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlK, gocui.ModNone, cursorUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlD, gocui.ModNone, cursorPageDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlU, gocui.ModNone, cursorPageUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeySpace, gocui.ModNone, cursorPageDown))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyBackspace, gocui.ModNone, cursorPageUp))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyBackspace2, gocui.ModNone, cursorPageUp))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlM, gocui.ModNone, cursorMoveMiddle))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlN, gocui.ModNone, nextSummary))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlP, gocui.ModNone, previousSummary))
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("detail", gocui.KeyEnter, gocui.ModNone, nextView))
 | 
			
		||||
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("msg", gocui.KeyEnter, gocui.ModNone, delMsg))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("detail", gocui.KeyEnter, gocui.ModNone, showMsg))
 | 
			
		||||
 | 
			
		||||
	//TODO Help Ctrl-h
 | 
			
		||||
 | 
			
		||||
	errs = append(errs, g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("side", gocui.KeyEnter, gocui.ModNone, getLine))
 | 
			
		||||
	//  errs = append(errs, g.SetKeybinding("msg", gocui.KeyEnter, gocui.ModNone, delMsg))
 | 
			
		||||
 | 
			
		||||
	for _, e := range errs {
 | 
			
		||||
		if e != nil {
 | 
			
		||||
			return e
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func nextView(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if v == nil {
 | 
			
		||||
		return g.SetCurrentView("side")
 | 
			
		||||
	}
 | 
			
		||||
	switch v.Name() {
 | 
			
		||||
	case "side":
 | 
			
		||||
		return g.SetCurrentView("summary")
 | 
			
		||||
	case "summary":
 | 
			
		||||
		return g.SetCurrentView("detail")
 | 
			
		||||
	case "detail":
 | 
			
		||||
		return g.SetCurrentView("side")
 | 
			
		||||
	default:
 | 
			
		||||
		return g.SetCurrentView("summary")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func previousView(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if v == nil {
 | 
			
		||||
		return g.SetCurrentView("side")
 | 
			
		||||
	}
 | 
			
		||||
	switch v.Name() {
 | 
			
		||||
	case "side":
 | 
			
		||||
		return g.SetCurrentView("side")
 | 
			
		||||
	case "summary":
 | 
			
		||||
		return g.SetCurrentView("side")
 | 
			
		||||
	case "detail":
 | 
			
		||||
		return g.SetCurrentView("summary")
 | 
			
		||||
	default:
 | 
			
		||||
		return g.SetCurrentView("side")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func movable(v *gocui.View, nextY int) (ok bool, yLimit int) {
 | 
			
		||||
	switch v.Name() {
 | 
			
		||||
	case "side":
 | 
			
		||||
		yLimit = len(scanHistory.ScanResults) - 1
 | 
			
		||||
		if yLimit < nextY {
 | 
			
		||||
			return false, yLimit
 | 
			
		||||
		}
 | 
			
		||||
		return true, yLimit
 | 
			
		||||
	case "summary":
 | 
			
		||||
		yLimit = len(currentScanResult.KnownCves) - 1
 | 
			
		||||
		if yLimit < nextY {
 | 
			
		||||
			return false, yLimit
 | 
			
		||||
		}
 | 
			
		||||
		return true, yLimit
 | 
			
		||||
	case "detail":
 | 
			
		||||
		if currentDetailLimitY < nextY {
 | 
			
		||||
			return false, currentDetailLimitY
 | 
			
		||||
		}
 | 
			
		||||
		return true, currentDetailLimitY
 | 
			
		||||
	default:
 | 
			
		||||
		return true, 0
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func pageUpDownJumpCount(v *gocui.View) int {
 | 
			
		||||
	var jump int
 | 
			
		||||
	switch v.Name() {
 | 
			
		||||
	case "side", "summary":
 | 
			
		||||
		jump = 8
 | 
			
		||||
	case "detail":
 | 
			
		||||
		jump = 30
 | 
			
		||||
	default:
 | 
			
		||||
		jump = 8
 | 
			
		||||
	}
 | 
			
		||||
	return jump
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// redraw views
 | 
			
		||||
func onMovingCursorRedrawView(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	switch v.Name() {
 | 
			
		||||
	case "summary":
 | 
			
		||||
		if err := redrawDetail(g); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	case "side":
 | 
			
		||||
		if err := changeHost(g, v); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cursorDown(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if v != nil {
 | 
			
		||||
		cx, cy := v.Cursor()
 | 
			
		||||
		ox, oy := v.Origin()
 | 
			
		||||
		//  ok,  := movable(v, oy+cy+1)
 | 
			
		||||
		//  _, maxY := v.Size()
 | 
			
		||||
		ok, _ := movable(v, oy+cy+1)
 | 
			
		||||
		//  log.Info(cy, oy, maxY, yLimit)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		if err := v.SetCursor(cx, cy+1); err != nil {
 | 
			
		||||
			if err := v.SetOrigin(ox, oy+1); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		onMovingCursorRedrawView(g, v)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cursorMoveTop(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if v != nil {
 | 
			
		||||
		cx, _ := v.Cursor()
 | 
			
		||||
		v.SetCursor(cx, 0)
 | 
			
		||||
	}
 | 
			
		||||
	onMovingCursorRedrawView(g, v)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cursorMoveBottom(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if v != nil {
 | 
			
		||||
		_, maxY := v.Size()
 | 
			
		||||
		cx, _ := v.Cursor()
 | 
			
		||||
		v.SetCursor(cx, maxY-1)
 | 
			
		||||
	}
 | 
			
		||||
	onMovingCursorRedrawView(g, v)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cursorMoveMiddle(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if v != nil {
 | 
			
		||||
		_, maxY := v.Size()
 | 
			
		||||
		cx, _ := v.Cursor()
 | 
			
		||||
		v.SetCursor(cx, maxY/2)
 | 
			
		||||
	}
 | 
			
		||||
	onMovingCursorRedrawView(g, v)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cursorPageDown(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	jump := pageUpDownJumpCount(v)
 | 
			
		||||
 | 
			
		||||
	if v != nil {
 | 
			
		||||
		cx, cy := v.Cursor()
 | 
			
		||||
		ox, oy := v.Origin()
 | 
			
		||||
		ok, yLimit := movable(v, oy+cy+jump)
 | 
			
		||||
		_, maxY := v.Size()
 | 
			
		||||
 | 
			
		||||
		if !ok {
 | 
			
		||||
			if yLimit < maxY {
 | 
			
		||||
				v.SetCursor(cx, yLimit)
 | 
			
		||||
			} else {
 | 
			
		||||
				v.SetCursor(cx, maxY-1)
 | 
			
		||||
				v.SetOrigin(ox, yLimit-maxY+1)
 | 
			
		||||
			}
 | 
			
		||||
		} else if yLimit < oy+jump+maxY {
 | 
			
		||||
			if yLimit < maxY {
 | 
			
		||||
				v.SetCursor(cx, yLimit)
 | 
			
		||||
			} else {
 | 
			
		||||
				v.SetOrigin(ox, yLimit-maxY+1)
 | 
			
		||||
				v.SetCursor(cx, maxY-1)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			v.SetCursor(cx, cy)
 | 
			
		||||
			v.SetOrigin(ox, oy+jump)
 | 
			
		||||
		}
 | 
			
		||||
		onMovingCursorRedrawView(g, v)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cursorUp(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if v != nil {
 | 
			
		||||
		ox, oy := v.Origin()
 | 
			
		||||
		cx, cy := v.Cursor()
 | 
			
		||||
		if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 {
 | 
			
		||||
			if err := v.SetOrigin(ox, oy-1); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	onMovingCursorRedrawView(g, v)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cursorPageUp(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	jump := pageUpDownJumpCount(v)
 | 
			
		||||
	if v != nil {
 | 
			
		||||
		cx, _ := v.Cursor()
 | 
			
		||||
		ox, oy := v.Origin()
 | 
			
		||||
		if err := v.SetOrigin(ox, oy-jump); err != nil {
 | 
			
		||||
			v.SetOrigin(ox, 0)
 | 
			
		||||
			v.SetCursor(cx, 0)
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
		onMovingCursorRedrawView(g, v)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func previousSummary(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if v != nil {
 | 
			
		||||
		// cursor to summary
 | 
			
		||||
		if err := g.SetCurrentView("summary"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		// move next line
 | 
			
		||||
		if err := cursorUp(g, g.CurrentView()); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		// cursor to detail
 | 
			
		||||
		if err := g.SetCurrentView("detail"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func nextSummary(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if v != nil {
 | 
			
		||||
		// cursor to summary
 | 
			
		||||
		if err := g.SetCurrentView("summary"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		// move next line
 | 
			
		||||
		if err := cursorDown(g, g.CurrentView()); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		// cursor to detail
 | 
			
		||||
		if err := g.SetCurrentView("detail"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func changeHost(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
 | 
			
		||||
	if err := g.DeleteView("summary"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := g.DeleteView("detail"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, cy := v.Cursor()
 | 
			
		||||
	l, err := v.Line(cy)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	serverName := strings.TrimSpace(l)
 | 
			
		||||
 | 
			
		||||
	for _, r := range scanHistory.ScanResults {
 | 
			
		||||
		if serverName == r.ServerName {
 | 
			
		||||
			currentScanResult = r
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := setSummaryLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := setDetailLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func redrawDetail(g *gocui.Gui) error {
 | 
			
		||||
	if err := g.DeleteView("detail"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := setDetailLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getLine(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	var l string
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	_, cy := v.Cursor()
 | 
			
		||||
	if l, err = v.Line(cy); err != nil {
 | 
			
		||||
		l = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	maxX, maxY := g.Size()
 | 
			
		||||
	if v, err := g.SetView("msg", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil {
 | 
			
		||||
		if err != gocui.ErrUnknownView {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Fprintln(v, l)
 | 
			
		||||
		if err := g.SetCurrentView("msg"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func showMsg(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	jump := 8
 | 
			
		||||
	_, cy := v.Cursor()
 | 
			
		||||
	_, oy := v.Origin()
 | 
			
		||||
	ok, yLimit := movable(v, oy+cy+jump)
 | 
			
		||||
	//  maxX, maxY := v.Size()
 | 
			
		||||
	_, maxY := v.Size()
 | 
			
		||||
 | 
			
		||||
	l := fmt.Sprintf("cy: %d, oy: %d, maxY: %d, yLimit: %d, curCve %d, ok: %v", cy, oy, maxY, yLimit, currentCveInfo, ok)
 | 
			
		||||
	//  if v, err := g.SetView("msg", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil {
 | 
			
		||||
	if v, err := g.SetView("msg", 10, maxY/2, 10+50, maxY/2+2); err != nil {
 | 
			
		||||
		if err != gocui.ErrUnknownView {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Fprintln(v, l)
 | 
			
		||||
		if err := g.SetCurrentView("msg"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func delMsg(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if err := g.DeleteView("msg"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := g.SetCurrentView("summary"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func quit(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	return gocui.ErrQuit
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func layout(g *gocui.Gui) error {
 | 
			
		||||
	if err := setSideLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := setSummaryLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := setDetailLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setSideLayout(g *gocui.Gui) error {
 | 
			
		||||
	_, maxY := g.Size()
 | 
			
		||||
	if v, err := g.SetView("side", -1, -1, 30, maxY); err != nil {
 | 
			
		||||
		if err != gocui.ErrUnknownView {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		v.Highlight = true
 | 
			
		||||
 | 
			
		||||
		for _, result := range scanHistory.ScanResults {
 | 
			
		||||
			fmt.Fprintln(v, result.ServerName)
 | 
			
		||||
		}
 | 
			
		||||
		currentScanResult = scanHistory.ScanResults[0]
 | 
			
		||||
		if err := g.SetCurrentView("side"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setSummaryLayout(g *gocui.Gui) error {
 | 
			
		||||
	maxX, maxY := g.Size()
 | 
			
		||||
	if v, err := g.SetView("summary", 30, -1, maxX, int(float64(maxY)*0.2)); err != nil {
 | 
			
		||||
		if err != gocui.ErrUnknownView {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		lines := summaryLines(currentScanResult)
 | 
			
		||||
		fmt.Fprintf(v, lines)
 | 
			
		||||
 | 
			
		||||
		v.Highlight = true
 | 
			
		||||
		v.Editable = false
 | 
			
		||||
		v.Wrap = false
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func summaryLines(data models.ScanResult) string {
 | 
			
		||||
	stable := uitable.New()
 | 
			
		||||
	stable.MaxColWidth = 1000
 | 
			
		||||
	stable.Wrap = false
 | 
			
		||||
 | 
			
		||||
	indexFormat := ""
 | 
			
		||||
	if len(data.KnownCves) < 10 {
 | 
			
		||||
		indexFormat = "[%1d]"
 | 
			
		||||
	} else if len(data.KnownCves) < 100 {
 | 
			
		||||
		indexFormat = "[%2d]"
 | 
			
		||||
	} else {
 | 
			
		||||
		indexFormat = "[%3d]"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, d := range data.KnownCves {
 | 
			
		||||
		var cols []string
 | 
			
		||||
		//  packs := []string{}
 | 
			
		||||
		//  for _, pack := range d.Packages {
 | 
			
		||||
		//      packs = append(packs, pack.Name)
 | 
			
		||||
		//  }
 | 
			
		||||
		if config.Conf.Lang == "ja" && 0 < d.CveDetail.Jvn.CvssScore() {
 | 
			
		||||
			summary := d.CveDetail.Jvn.Title
 | 
			
		||||
			cols = []string{
 | 
			
		||||
				fmt.Sprintf(indexFormat, i+1),
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				fmt.Sprintf("|  %-4.1f(%s)",
 | 
			
		||||
					d.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
					d.CveDetail.Jvn.Severity,
 | 
			
		||||
				),
 | 
			
		||||
				//  strings.Join(packs, ","),
 | 
			
		||||
				summary,
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			summary := d.CveDetail.Nvd.Summary
 | 
			
		||||
 | 
			
		||||
			var cvssScore string
 | 
			
		||||
			if d.CveDetail.CvssScore("en") <= 0 {
 | 
			
		||||
				cvssScore = "| ?"
 | 
			
		||||
			} else {
 | 
			
		||||
				cvssScore = fmt.Sprintf("| %-4.1f(%s)",
 | 
			
		||||
					d.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
					d.CveDetail.Nvd.Severity(),
 | 
			
		||||
				)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			cols = []string{
 | 
			
		||||
				fmt.Sprintf(indexFormat, i+1),
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				cvssScore,
 | 
			
		||||
				summary,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		icols := make([]interface{}, len(cols))
 | 
			
		||||
		for j := range cols {
 | 
			
		||||
			icols[j] = cols[j]
 | 
			
		||||
		}
 | 
			
		||||
		stable.AddRow(icols...)
 | 
			
		||||
	}
 | 
			
		||||
	// ignore UnknownCves
 | 
			
		||||
	return fmt.Sprintf("%s", stable)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setDetailLayout(g *gocui.Gui) error {
 | 
			
		||||
	maxX, maxY := g.Size()
 | 
			
		||||
 | 
			
		||||
	summaryView, err := g.View("summary")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	_, cy := summaryView.Cursor()
 | 
			
		||||
	_, oy := summaryView.Origin()
 | 
			
		||||
	currentCveInfo = cy + oy
 | 
			
		||||
 | 
			
		||||
	if v, err := g.SetView("detail", 30, int(float64(maxY)*0.2), maxX, maxY); err != nil {
 | 
			
		||||
		if err != gocui.ErrUnknownView {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		//  text := report.ToPlainTextDetailsLangEn(
 | 
			
		||||
		//      currentScanResult.KnownCves[currentCveInfo],
 | 
			
		||||
		//      currentScanResult.Family)
 | 
			
		||||
 | 
			
		||||
		//TODO error handling
 | 
			
		||||
		text, err := detailLines()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Fprint(v, text)
 | 
			
		||||
		v.Editable = false
 | 
			
		||||
		v.Wrap = true
 | 
			
		||||
 | 
			
		||||
		currentDetailLimitY = len(strings.Split(text, "\n")) - 1
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dataForTmpl struct {
 | 
			
		||||
	CveID            string
 | 
			
		||||
	CvssScore        string
 | 
			
		||||
	CvssVector       string
 | 
			
		||||
	CvssSeverity     string
 | 
			
		||||
	Summary          string
 | 
			
		||||
	VulnSiteLinks    []string
 | 
			
		||||
	References       []cve.Reference
 | 
			
		||||
	Packages         []string
 | 
			
		||||
	CpeNames         []models.CpeName
 | 
			
		||||
	PublishedDate    time.Time
 | 
			
		||||
	LastModifiedDate time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detailLines() (string, error) {
 | 
			
		||||
	cveInfo := currentScanResult.KnownCves[currentCveInfo]
 | 
			
		||||
	cveID := cveInfo.CveDetail.CveID
 | 
			
		||||
 | 
			
		||||
	tmpl, err := template.New("detail").Parse(detailTemplate())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var cvssSeverity, cvssVector, summary string
 | 
			
		||||
	var refs []cve.Reference
 | 
			
		||||
	switch {
 | 
			
		||||
	case config.Conf.Lang == "ja" &&
 | 
			
		||||
		0 < cveInfo.CveDetail.Jvn.CvssScore():
 | 
			
		||||
		jvn := cveInfo.CveDetail.Jvn
 | 
			
		||||
		cvssSeverity = jvn.Severity
 | 
			
		||||
		cvssVector = jvn.Vector
 | 
			
		||||
		summary = fmt.Sprintf("%s\n%s", jvn.Title, jvn.Summary)
 | 
			
		||||
		refs = jvn.References
 | 
			
		||||
	default:
 | 
			
		||||
		nvd := cveInfo.CveDetail.Nvd
 | 
			
		||||
		cvssSeverity = nvd.Severity()
 | 
			
		||||
		cvssVector = nvd.CvssVector()
 | 
			
		||||
		summary = nvd.Summary
 | 
			
		||||
		refs = nvd.References
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	links := []string{
 | 
			
		||||
		fmt.Sprintf("[NVD]( %s )", fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID)),
 | 
			
		||||
		fmt.Sprintf("[MITRE]( %s )", fmt.Sprintf("%s%s", mitreBaseURL, cveID)),
 | 
			
		||||
		fmt.Sprintf("[CveDetais]( %s )", fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID)),
 | 
			
		||||
		fmt.Sprintf("[CVSSv2 Caluclator]( %s )", fmt.Sprintf(cvssV2CalcURLTemplate, cveID, cvssVector)),
 | 
			
		||||
	}
 | 
			
		||||
	dlinks := distroLinks(cveInfo, currentScanResult.Family)
 | 
			
		||||
	for _, link := range dlinks {
 | 
			
		||||
		links = append(links, fmt.Sprintf("[%s]( %s )", link.title, link.url))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var cvssScore string
 | 
			
		||||
	if cveInfo.CveDetail.CvssScore(config.Conf.Lang) == -1 {
 | 
			
		||||
		cvssScore = "?"
 | 
			
		||||
	} else {
 | 
			
		||||
		cvssScore = fmt.Sprintf("%4.1f", cveInfo.CveDetail.CvssScore(config.Conf.Lang))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	packages := []string{}
 | 
			
		||||
	for _, pack := range cveInfo.Packages {
 | 
			
		||||
		packages = append(packages,
 | 
			
		||||
			fmt.Sprintf(
 | 
			
		||||
				"%s -> %s",
 | 
			
		||||
				pack.ToStringCurrentVersion(),
 | 
			
		||||
				pack.ToStringNewVersion()))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	data := dataForTmpl{
 | 
			
		||||
		CveID:         cveID,
 | 
			
		||||
		CvssScore:     cvssScore,
 | 
			
		||||
		CvssSeverity:  cvssSeverity,
 | 
			
		||||
		CvssVector:    cvssVector,
 | 
			
		||||
		Summary:       summary,
 | 
			
		||||
		VulnSiteLinks: links,
 | 
			
		||||
		References:    refs,
 | 
			
		||||
		Packages:      packages,
 | 
			
		||||
		CpeNames:      cveInfo.CpeNames,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	buf := bytes.NewBuffer(nil) // create empty buffer
 | 
			
		||||
	if err := tmpl.Execute(buf, data); err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return string(buf.Bytes()), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//  * {{.Name}}-{{.Version}}-{{.Release}}
 | 
			
		||||
 | 
			
		||||
func detailTemplate() string {
 | 
			
		||||
	return `
 | 
			
		||||
{{.CveID}}
 | 
			
		||||
==============
 | 
			
		||||
 | 
			
		||||
CVSS Score
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
{{.CvssScore}} ({{.CvssSeverity}}) {{.CvssVector}}
 | 
			
		||||
 | 
			
		||||
Summary
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
 {{.Summary }}
 | 
			
		||||
 | 
			
		||||
Package/CPE
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
{{range $pack := .Packages -}}
 | 
			
		||||
* {{$pack}}
 | 
			
		||||
{{end -}}
 | 
			
		||||
{{range .CpeNames -}}
 | 
			
		||||
* {{.Name}}
 | 
			
		||||
{{end}}
 | 
			
		||||
Links
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
{{range $link := .VulnSiteLinks -}}
 | 
			
		||||
* {{$link}}
 | 
			
		||||
{{end}}
 | 
			
		||||
References
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
{{range .References -}}
 | 
			
		||||
* [{{.Source}}]( {{.Link}} )
 | 
			
		||||
{{end}}
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										334
									
								
								report/util.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										334
									
								
								report/util.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,334 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/gosuri/uitable"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func toPlainText(scanResult models.ScanResult) (string, error) {
 | 
			
		||||
	hostinfo := fmt.Sprintf(
 | 
			
		||||
		"%s (%s %s)",
 | 
			
		||||
		scanResult.ServerName,
 | 
			
		||||
		scanResult.Family,
 | 
			
		||||
		scanResult.Release,
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	var buffer bytes.Buffer
 | 
			
		||||
	for i := 0; i < len(hostinfo); i++ {
 | 
			
		||||
		buffer.WriteString("=")
 | 
			
		||||
	}
 | 
			
		||||
	header := fmt.Sprintf("%s\n%s", hostinfo, buffer.String())
 | 
			
		||||
 | 
			
		||||
	if len(scanResult.KnownCves) == 0 && len(scanResult.UnknownCves) == 0 {
 | 
			
		||||
		return fmt.Sprintf(`
 | 
			
		||||
%s
 | 
			
		||||
No unsecure packages.
 | 
			
		||||
`, header), nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	summary := ToPlainTextSummary(scanResult)
 | 
			
		||||
	scoredReport, unscoredReport := []string{}, []string{}
 | 
			
		||||
	scoredReport, unscoredReport = toPlainTextDetails(scanResult, scanResult.Family)
 | 
			
		||||
 | 
			
		||||
	scored := strings.Join(scoredReport, "\n\n")
 | 
			
		||||
	unscored := strings.Join(unscoredReport, "\n\n")
 | 
			
		||||
	detail := fmt.Sprintf(`
 | 
			
		||||
%s
 | 
			
		||||
 | 
			
		||||
%s
 | 
			
		||||
`,
 | 
			
		||||
		scored,
 | 
			
		||||
		unscored,
 | 
			
		||||
	)
 | 
			
		||||
	text := fmt.Sprintf("%s\n%s\n%s\n", header, summary, detail)
 | 
			
		||||
 | 
			
		||||
	return text, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToPlainTextSummary format summary for plain text.
 | 
			
		||||
func ToPlainTextSummary(r models.ScanResult) string {
 | 
			
		||||
	stable := uitable.New()
 | 
			
		||||
	stable.MaxColWidth = 84
 | 
			
		||||
	stable.Wrap = true
 | 
			
		||||
	cves := append(r.KnownCves, r.UnknownCves...)
 | 
			
		||||
	for _, d := range cves {
 | 
			
		||||
		var scols []string
 | 
			
		||||
 | 
			
		||||
		switch {
 | 
			
		||||
		case config.Conf.Lang == "ja" &&
 | 
			
		||||
			d.CveDetail.Jvn.ID != 0 &&
 | 
			
		||||
			0 < d.CveDetail.CvssScore("ja"):
 | 
			
		||||
 | 
			
		||||
			summary := d.CveDetail.Jvn.Title
 | 
			
		||||
			scols = []string{
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				fmt.Sprintf("%-4.1f (%s)",
 | 
			
		||||
					d.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
					d.CveDetail.Jvn.Severity,
 | 
			
		||||
				),
 | 
			
		||||
				summary,
 | 
			
		||||
			}
 | 
			
		||||
		case 0 < d.CveDetail.CvssScore("en"):
 | 
			
		||||
			summary := d.CveDetail.Nvd.Summary
 | 
			
		||||
			scols = []string{
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				fmt.Sprintf("%-4.1f",
 | 
			
		||||
					d.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
				),
 | 
			
		||||
				summary,
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
			scols = []string{
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				"?",
 | 
			
		||||
				d.CveDetail.Nvd.Summary,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cols := make([]interface{}, len(scols))
 | 
			
		||||
		for i := range cols {
 | 
			
		||||
			cols[i] = scols[i]
 | 
			
		||||
		}
 | 
			
		||||
		stable.AddRow(cols...)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s", stable)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//TODO Distro Advisory
 | 
			
		||||
func toPlainTextDetails(data models.ScanResult, osFamily string) (scoredReport, unscoredReport []string) {
 | 
			
		||||
	for _, cve := range data.KnownCves {
 | 
			
		||||
		switch config.Conf.Lang {
 | 
			
		||||
		case "en":
 | 
			
		||||
			if cve.CveDetail.Nvd.ID != 0 {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, toPlainTextDetailsLangEn(cve, osFamily))
 | 
			
		||||
			} else {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, toPlainTextUnknownCve(cve, osFamily))
 | 
			
		||||
			}
 | 
			
		||||
		case "ja":
 | 
			
		||||
			if cve.CveDetail.Jvn.ID != 0 {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, toPlainTextDetailsLangJa(cve, osFamily))
 | 
			
		||||
			} else if cve.CveDetail.Nvd.ID != 0 {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, toPlainTextDetailsLangEn(cve, osFamily))
 | 
			
		||||
			} else {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, toPlainTextUnknownCve(cve, osFamily))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, cve := range data.UnknownCves {
 | 
			
		||||
		unscoredReport = append(
 | 
			
		||||
			unscoredReport, toPlainTextUnknownCve(cve, osFamily))
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toPlainTextUnknownCve(cveInfo models.CveInfo, osFamily string) string {
 | 
			
		||||
	cveID := cveInfo.CveDetail.CveID
 | 
			
		||||
	dtable := uitable.New()
 | 
			
		||||
	dtable.MaxColWidth = 100
 | 
			
		||||
	dtable.Wrap = true
 | 
			
		||||
	dtable.AddRow(cveID)
 | 
			
		||||
	dtable.AddRow("-------------")
 | 
			
		||||
	dtable.AddRow("Score", "?")
 | 
			
		||||
	dtable.AddRow("NVD",
 | 
			
		||||
		fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID))
 | 
			
		||||
	dtable.AddRow("CVE Details",
 | 
			
		||||
		fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID))
 | 
			
		||||
 | 
			
		||||
	dlinks := distroLinks(cveInfo, osFamily)
 | 
			
		||||
	for _, link := range dlinks {
 | 
			
		||||
		dtable.AddRow(link.title, link.url)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("%s", dtable)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toPlainTextDetailsLangJa(cveInfo models.CveInfo, osFamily string) string {
 | 
			
		||||
 | 
			
		||||
	cveDetail := cveInfo.CveDetail
 | 
			
		||||
	cveID := cveDetail.CveID
 | 
			
		||||
	jvn := cveDetail.Jvn
 | 
			
		||||
 | 
			
		||||
	dtable := uitable.New()
 | 
			
		||||
	//TODO resize
 | 
			
		||||
	dtable.MaxColWidth = 100
 | 
			
		||||
	dtable.Wrap = true
 | 
			
		||||
	dtable.AddRow(cveID)
 | 
			
		||||
	dtable.AddRow("-------------")
 | 
			
		||||
	if score := cveDetail.Jvn.CvssScore(); 0 < score {
 | 
			
		||||
		dtable.AddRow("Score",
 | 
			
		||||
			fmt.Sprintf("%4.1f (%s)",
 | 
			
		||||
				cveDetail.Jvn.CvssScore(),
 | 
			
		||||
				jvn.Severity,
 | 
			
		||||
			))
 | 
			
		||||
	} else {
 | 
			
		||||
		dtable.AddRow("Score", "?")
 | 
			
		||||
	}
 | 
			
		||||
	dtable.AddRow("Vector", jvn.Vector)
 | 
			
		||||
	dtable.AddRow("Title", jvn.Title)
 | 
			
		||||
	dtable.AddRow("Description", jvn.Summary)
 | 
			
		||||
 | 
			
		||||
	dtable.AddRow("JVN", jvn.Link())
 | 
			
		||||
	dtable.AddRow("NVD", fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID))
 | 
			
		||||
	dtable.AddRow("MITRE", fmt.Sprintf("%s%s", mitreBaseURL, cveID))
 | 
			
		||||
	dtable.AddRow("CVE Details", fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID))
 | 
			
		||||
	dtable.AddRow("CVSS Claculator", cveDetail.CvssV2CalculatorLink("ja"))
 | 
			
		||||
 | 
			
		||||
	dlinks := distroLinks(cveInfo, osFamily)
 | 
			
		||||
	for _, link := range dlinks {
 | 
			
		||||
		dtable.AddRow(link.title, link.url)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dtable = addPackageInfos(dtable, cveInfo.Packages)
 | 
			
		||||
	dtable = addCpeNames(dtable, cveInfo.CpeNames)
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("%s", dtable)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toPlainTextDetailsLangEn(d models.CveInfo, osFamily string) string {
 | 
			
		||||
	cveDetail := d.CveDetail
 | 
			
		||||
	cveID := cveDetail.CveID
 | 
			
		||||
	nvd := cveDetail.Nvd
 | 
			
		||||
 | 
			
		||||
	dtable := uitable.New()
 | 
			
		||||
	//TODO resize
 | 
			
		||||
	dtable.MaxColWidth = 100
 | 
			
		||||
	dtable.Wrap = true
 | 
			
		||||
	dtable.AddRow(cveID)
 | 
			
		||||
	dtable.AddRow("-------------")
 | 
			
		||||
 | 
			
		||||
	if score := cveDetail.Nvd.CvssScore(); 0 < score {
 | 
			
		||||
		dtable.AddRow("Score",
 | 
			
		||||
			fmt.Sprintf("%4.1f (%s)",
 | 
			
		||||
				cveDetail.Nvd.CvssScore(),
 | 
			
		||||
				nvd.Severity(),
 | 
			
		||||
			))
 | 
			
		||||
	} else {
 | 
			
		||||
		dtable.AddRow("Score", "?")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dtable.AddRow("Vector", nvd.CvssVector())
 | 
			
		||||
	dtable.AddRow("Summary", nvd.Summary)
 | 
			
		||||
	dtable.AddRow("NVD", fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID))
 | 
			
		||||
	dtable.AddRow("MITRE", fmt.Sprintf("%s%s", mitreBaseURL, cveID))
 | 
			
		||||
	dtable.AddRow("CVE Details", fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID))
 | 
			
		||||
	dtable.AddRow("CVSS Claculator", cveDetail.CvssV2CalculatorLink("en"))
 | 
			
		||||
 | 
			
		||||
	links := distroLinks(d, osFamily)
 | 
			
		||||
	for _, link := range links {
 | 
			
		||||
		dtable.AddRow(link.title, link.url)
 | 
			
		||||
	}
 | 
			
		||||
	dtable = addPackageInfos(dtable, d.Packages)
 | 
			
		||||
	dtable = addCpeNames(dtable, d.CpeNames)
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("%s\n", dtable)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type distroLink struct {
 | 
			
		||||
	title string
 | 
			
		||||
	url   string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// addVendorSite add Vendor site of the CVE to table
 | 
			
		||||
func distroLinks(cveInfo models.CveInfo, osFamily string) []distroLink {
 | 
			
		||||
	cveID := cveInfo.CveDetail.CveID
 | 
			
		||||
	switch osFamily {
 | 
			
		||||
	case "rhel", "centos":
 | 
			
		||||
		links := []distroLink{
 | 
			
		||||
			{
 | 
			
		||||
				"RHEL-CVE",
 | 
			
		||||
				fmt.Sprintf("%s/%s", redhatSecurityBaseURL, cveID),
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
		for _, advisory := range cveInfo.DistroAdvisories {
 | 
			
		||||
			aidURL := strings.Replace(advisory.AdvisoryID, ":", "-", -1)
 | 
			
		||||
			links = append(links, distroLink{
 | 
			
		||||
				//  "RHEL-errata",
 | 
			
		||||
				advisory.AdvisoryID,
 | 
			
		||||
				fmt.Sprintf(redhatRHSABaseBaseURL, aidURL),
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
		return links
 | 
			
		||||
	case "amazon":
 | 
			
		||||
		links := []distroLink{
 | 
			
		||||
			{
 | 
			
		||||
				"RHEL-CVE",
 | 
			
		||||
				fmt.Sprintf("%s/%s", redhatSecurityBaseURL, cveID),
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
		for _, advisory := range cveInfo.DistroAdvisories {
 | 
			
		||||
			links = append(links, distroLink{
 | 
			
		||||
				//  "Amazon-ALAS",
 | 
			
		||||
				advisory.AdvisoryID,
 | 
			
		||||
				fmt.Sprintf(amazonSecurityBaseURL, advisory.AdvisoryID),
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
		return links
 | 
			
		||||
	case "ubuntu":
 | 
			
		||||
		return []distroLink{
 | 
			
		||||
			{
 | 
			
		||||
				"Ubuntu-CVE",
 | 
			
		||||
				fmt.Sprintf("%s/%s", ubuntuSecurityBaseURL, cveID),
 | 
			
		||||
			},
 | 
			
		||||
			//TODO Ubuntu USN
 | 
			
		||||
		}
 | 
			
		||||
	case "debian":
 | 
			
		||||
		return []distroLink{
 | 
			
		||||
			{
 | 
			
		||||
				"Debian-CVE",
 | 
			
		||||
				fmt.Sprintf("%s/%s", debianTrackerBaseURL, cveID),
 | 
			
		||||
			},
 | 
			
		||||
			//  TODO Debian dsa
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		return []distroLink{}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//TODO
 | 
			
		||||
// addPackageInfos add package information related the CVE to table
 | 
			
		||||
func addPackageInfos(table *uitable.Table, packs []models.PackageInfo) *uitable.Table {
 | 
			
		||||
	for i, p := range packs {
 | 
			
		||||
		var title string
 | 
			
		||||
		if i == 0 {
 | 
			
		||||
			title = "Package/CPE"
 | 
			
		||||
		}
 | 
			
		||||
		ver := fmt.Sprintf(
 | 
			
		||||
			"%s -> %s", p.ToStringCurrentVersion(), p.ToStringNewVersion())
 | 
			
		||||
		table.AddRow(title, ver)
 | 
			
		||||
	}
 | 
			
		||||
	return table
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func addCpeNames(table *uitable.Table, names []models.CpeName) *uitable.Table {
 | 
			
		||||
	for _, p := range names {
 | 
			
		||||
		table.AddRow("CPE", fmt.Sprintf("%s", p.Name))
 | 
			
		||||
	}
 | 
			
		||||
	return table
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								report/writer.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								report/writer.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import "github.com/future-architect/vuls/models"
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	nvdBaseURL            = "https://web.nvd.nist.gov/view/vuln/detail"
 | 
			
		||||
	mitreBaseURL          = "https://cve.mitre.org/cgi-bin/cvename.cgi?name="
 | 
			
		||||
	cveDetailsBaseURL     = "http://www.cvedetails.com/cve"
 | 
			
		||||
	cvssV2CalcURLTemplate = "https://nvd.nist.gov/cvss/v2-calculator?name=%s&vector=%s"
 | 
			
		||||
 | 
			
		||||
	redhatSecurityBaseURL = "https://access.redhat.com/security/cve"
 | 
			
		||||
	redhatRHSABaseBaseURL = "https://rhn.redhat.com/errata/%s.html"
 | 
			
		||||
	amazonSecurityBaseURL = "https://alas.aws.amazon.com/%s.html"
 | 
			
		||||
 | 
			
		||||
	ubuntuSecurityBaseURL = "http://people.ubuntu.com/~ubuntu-security/cve"
 | 
			
		||||
	debianTrackerBaseURL  = "https://security-tracker.debian.org/tracker"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ResultWriter Interface
 | 
			
		||||
type ResultWriter interface {
 | 
			
		||||
	Write([]models.ScanResult) error
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										655
									
								
								scan/debian.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										655
									
								
								scan/debian.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,655 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/cveapi"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// inherit OsTypeInterface
 | 
			
		||||
type debian struct {
 | 
			
		||||
	linux
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewDebian is constructor
 | 
			
		||||
func newDebian(c config.ServerInfo) *debian {
 | 
			
		||||
	d := &debian{}
 | 
			
		||||
	d.log = util.NewCustomLogger(c)
 | 
			
		||||
	return d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Ubuntu, Debian
 | 
			
		||||
// https://github.com/serverspec/specinfra/blob/master/lib/specinfra/helper/detect_os/debian.rb
 | 
			
		||||
func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface) {
 | 
			
		||||
 | 
			
		||||
	deb = newDebian(c)
 | 
			
		||||
 | 
			
		||||
	// set sudo option flag
 | 
			
		||||
	c.SudoOpt = config.SudoOption{ExecBySudo: true}
 | 
			
		||||
	deb.setServerInfo(c)
 | 
			
		||||
 | 
			
		||||
	if r := sshExec(c, "ls /etc/debian_version", noSudo); !r.isSuccess() {
 | 
			
		||||
		Log.Debugf("Not Debian like Linux. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
		return false, deb
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := sshExec(c, "lsb_release -ir", noSudo); r.isSuccess() {
 | 
			
		||||
		//  e.g.
 | 
			
		||||
		//  root@fa3ec524be43:/# lsb_release -ir
 | 
			
		||||
		//  Distributor ID:	Ubuntu
 | 
			
		||||
		//  Release:	14.04
 | 
			
		||||
		re, _ := regexp.Compile(
 | 
			
		||||
			`(?s)^Distributor ID:\s*(.+?)\n*Release:\s*(.+?)$`)
 | 
			
		||||
		result := re.FindStringSubmatch(trim(r.Stdout))
 | 
			
		||||
 | 
			
		||||
		if len(result) == 0 {
 | 
			
		||||
			deb.setDistributionInfo("debian/ubuntu", "unknown")
 | 
			
		||||
			Log.Warnf(
 | 
			
		||||
				"Unknown Debian/Ubuntu version. lsb_release -ir: %s, Host: %s:%s",
 | 
			
		||||
				r.Stdout, c.Host, c.Port)
 | 
			
		||||
		} else {
 | 
			
		||||
			distro := strings.ToLower(trim(result[1]))
 | 
			
		||||
			deb.setDistributionInfo(distro, trim(result[2]))
 | 
			
		||||
		}
 | 
			
		||||
		return true, deb
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := sshExec(c, "cat /etc/lsb-release", noSudo); r.isSuccess() {
 | 
			
		||||
		//  e.g.
 | 
			
		||||
		//  DISTRIB_ID=Ubuntu
 | 
			
		||||
		//  DISTRIB_RELEASE=14.04
 | 
			
		||||
		//  DISTRIB_CODENAME=trusty
 | 
			
		||||
		//  DISTRIB_DESCRIPTION="Ubuntu 14.04.2 LTS"
 | 
			
		||||
		re, _ := regexp.Compile(
 | 
			
		||||
			`(?s)^DISTRIB_ID=(.+?)\n*DISTRIB_RELEASE=(.+?)\n.*$`)
 | 
			
		||||
		result := re.FindStringSubmatch(trim(r.Stdout))
 | 
			
		||||
		if len(result) == 0 {
 | 
			
		||||
			Log.Warnf(
 | 
			
		||||
				"Unknown Debian/Ubuntu. cat /etc/lsb-release: %s, Host: %s:%s",
 | 
			
		||||
				r.Stdout, c.Host, c.Port)
 | 
			
		||||
			deb.setDistributionInfo("debian/ubuntu", "unknown")
 | 
			
		||||
		} else {
 | 
			
		||||
			distro := strings.ToLower(trim(result[1]))
 | 
			
		||||
			deb.setDistributionInfo(distro, trim(result[2]))
 | 
			
		||||
		}
 | 
			
		||||
		return true, deb
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Debian
 | 
			
		||||
	cmd := "cat /etc/debian_version"
 | 
			
		||||
	if r := sshExec(c, cmd, noSudo); r.isSuccess() {
 | 
			
		||||
		deb.setDistributionInfo("debian", trim(r.Stdout))
 | 
			
		||||
		return true, deb
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Debugf("Not Debian like Linux. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
	return false, deb
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func trim(str string) string {
 | 
			
		||||
	return strings.TrimSpace(str)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) install() error {
 | 
			
		||||
 | 
			
		||||
	// apt-get update
 | 
			
		||||
	o.log.Infof("apt-get update...")
 | 
			
		||||
	cmd := util.PrependProxyEnv("apt-get update")
 | 
			
		||||
	if r := o.ssh(cmd, sudo); !r.isSuccess() {
 | 
			
		||||
		msg := fmt.Sprintf("Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
			cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
		o.log.Errorf(msg)
 | 
			
		||||
		return fmt.Errorf(msg)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.Family == "debian" {
 | 
			
		||||
		// install aptitude
 | 
			
		||||
		cmd = util.PrependProxyEnv("apt-get install --force-yes -y aptitude")
 | 
			
		||||
		if r := o.ssh(cmd, sudo); !r.isSuccess() {
 | 
			
		||||
			msg := fmt.Sprintf("Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
				cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
			o.log.Errorf(msg)
 | 
			
		||||
			return fmt.Errorf(msg)
 | 
			
		||||
		}
 | 
			
		||||
		o.log.Infof("Installed: aptitude")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// install unattended-upgrades
 | 
			
		||||
	if !config.Conf.UseUnattendedUpgrades {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := o.ssh("type unattended-upgrade", noSudo); r.isSuccess() {
 | 
			
		||||
		o.log.Infof(
 | 
			
		||||
			"Ignored: unattended-upgrade already installed")
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd = util.PrependProxyEnv(
 | 
			
		||||
		"apt-get install --force-yes -y unattended-upgrades")
 | 
			
		||||
	if r := o.ssh(cmd, sudo); !r.isSuccess() {
 | 
			
		||||
		msg := fmt.Sprintf("Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
			cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
		o.log.Errorf(msg)
 | 
			
		||||
		return fmt.Errorf(msg)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o.log.Infof("Installed: unattended-upgrades")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) scanPackages() error {
 | 
			
		||||
	var err error
 | 
			
		||||
	var packs []models.PackageInfo
 | 
			
		||||
	if packs, err = o.scanInstalledPackages(); err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan installed packages")
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.setPackages(packs)
 | 
			
		||||
 | 
			
		||||
	var unsecurePacks []CvePacksInfo
 | 
			
		||||
	if unsecurePacks, err = o.scanUnsecurePackages(packs); err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan valnerable packages")
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.setUnsecurePackages(unsecurePacks)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) scanInstalledPackages() (packs []models.PackageInfo, err error) {
 | 
			
		||||
	r := o.ssh("dpkg-query -W", noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return packs, fmt.Errorf(
 | 
			
		||||
			"Failed to scan packages. status: %d, stdout:%s, stderr: %s",
 | 
			
		||||
			r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//  e.g.
 | 
			
		||||
	//  curl	7.19.7-40.el6_6.4
 | 
			
		||||
	//  openldap	2.4.39-8.el6
 | 
			
		||||
	lines := strings.Split(r.Stdout, "\n")
 | 
			
		||||
	for _, line := range lines {
 | 
			
		||||
		if trimmed := strings.TrimSpace(line); len(trimmed) != 0 {
 | 
			
		||||
			name, version, err := o.parseScanedPackagesLine(trimmed)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, fmt.Errorf(
 | 
			
		||||
					"Debian: Failed to parse package line: %s", line)
 | 
			
		||||
			}
 | 
			
		||||
			packs = append(packs, models.PackageInfo{
 | 
			
		||||
				Name:    name,
 | 
			
		||||
				Version: version,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) parseScanedPackagesLine(line string) (name, version string, err error) {
 | 
			
		||||
	re, _ := regexp.Compile(`^([^\t']+)\t(.+)$`)
 | 
			
		||||
	result := re.FindStringSubmatch(line)
 | 
			
		||||
	if len(result) == 3 {
 | 
			
		||||
		// remove :amd64, i386...
 | 
			
		||||
		name = regexp.MustCompile(":.+").ReplaceAllString(result[1], "")
 | 
			
		||||
		version = result[2]
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return "", "", fmt.Errorf("Unknown format: %s", line)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//  unattended-upgrade command need to check security upgrades).
 | 
			
		||||
func (o *debian) checkRequiredPackagesInstalled() error {
 | 
			
		||||
 | 
			
		||||
	if o.Family == "debian" {
 | 
			
		||||
		if r := o.ssh("test -f /usr/bin/aptitude", sudo); !r.isSuccess() {
 | 
			
		||||
			msg := "aptitude is not installed"
 | 
			
		||||
			o.log.Errorf(msg)
 | 
			
		||||
			return fmt.Errorf(msg)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !config.Conf.UseUnattendedUpgrades {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := o.ssh("type unattended-upgrade", noSudo); !r.isSuccess() {
 | 
			
		||||
		msg := "unattended-upgrade is not installed"
 | 
			
		||||
		o.log.Errorf(msg)
 | 
			
		||||
		return fmt.Errorf(msg)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//TODO return whether already expired.
 | 
			
		||||
func (o *debian) scanUnsecurePackages(packs []models.PackageInfo) ([]CvePacksInfo, error) {
 | 
			
		||||
	//  cmd := prependProxyEnv(conf.HTTPProxy, "apt-get update | cat; echo 1")
 | 
			
		||||
	cmd := util.PrependProxyEnv("apt-get update")
 | 
			
		||||
	if r := o.ssh(cmd, sudo); !r.isSuccess() {
 | 
			
		||||
		return nil, fmt.Errorf(
 | 
			
		||||
			"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
			cmd, r.ExitStatus, r.Stdout, r.Stderr,
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var upgradablePackNames []string
 | 
			
		||||
	var err error
 | 
			
		||||
	if config.Conf.UseUnattendedUpgrades {
 | 
			
		||||
		upgradablePackNames, err = o.GetUnsecurePackNamesUsingUnattendedUpgrades()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return []CvePacksInfo{}, err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		upgradablePackNames, err = o.GetUpgradablePackNames()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return []CvePacksInfo{}, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Convert package name to PackageInfo struct
 | 
			
		||||
	var unsecurePacks []models.PackageInfo
 | 
			
		||||
	for _, name := range upgradablePackNames {
 | 
			
		||||
		for _, pack := range packs {
 | 
			
		||||
			if pack.Name == name {
 | 
			
		||||
				unsecurePacks = append(unsecurePacks, pack)
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	unsecurePacks, err = o.fillCandidateVersion(unsecurePacks)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Collect CVE information of upgradable packages
 | 
			
		||||
	cvePacksInfos, err := o.scanPackageCveInfos(unsecurePacks)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to scan unsecure packages. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cvePacksInfos, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) fillCandidateVersion(packs []models.PackageInfo) ([]models.PackageInfo, error) {
 | 
			
		||||
	reqChan := make(chan models.PackageInfo, len(packs))
 | 
			
		||||
	resChan := make(chan models.PackageInfo, len(packs))
 | 
			
		||||
	errChan := make(chan error, len(packs))
 | 
			
		||||
	defer close(resChan)
 | 
			
		||||
	defer close(errChan)
 | 
			
		||||
	defer close(reqChan)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		for _, pack := range packs {
 | 
			
		||||
			reqChan <- pack
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	timeout := time.After(5 * 60 * time.Second)
 | 
			
		||||
	concurrency := 5
 | 
			
		||||
	tasks := util.GenWorkers(concurrency)
 | 
			
		||||
	for range packs {
 | 
			
		||||
		tasks <- func() {
 | 
			
		||||
			select {
 | 
			
		||||
			case pack := <-reqChan:
 | 
			
		||||
				func(p models.PackageInfo) {
 | 
			
		||||
					cmd := fmt.Sprintf("apt-cache policy %s", p.Name)
 | 
			
		||||
					r := o.ssh(cmd, sudo)
 | 
			
		||||
					if !r.isSuccess() {
 | 
			
		||||
						errChan <- fmt.Errorf(
 | 
			
		||||
							"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
							cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
						return
 | 
			
		||||
					}
 | 
			
		||||
					ver, err := o.parseAptCachePolicy(r.Stdout, p.Name)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						errChan <- fmt.Errorf("Failed to parse %s", err)
 | 
			
		||||
					}
 | 
			
		||||
					p.NewVersion = ver.Candidate
 | 
			
		||||
					resChan <- p
 | 
			
		||||
				}(pack)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	result := []models.PackageInfo{}
 | 
			
		||||
	for i := 0; i < len(packs); i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case pack := <-resChan:
 | 
			
		||||
			result = append(result, pack)
 | 
			
		||||
			o.log.Infof("(%d/%d) Upgradable: %s-%s -> %s",
 | 
			
		||||
				i+1, len(packs), pack.Name, pack.Version, pack.NewVersion)
 | 
			
		||||
		case err := <-errChan:
 | 
			
		||||
			return nil, err
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			return nil, fmt.Errorf("Timeout fillCandidateVersion.")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return result, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) GetUnsecurePackNamesUsingUnattendedUpgrades() (packNames []string, err error) {
 | 
			
		||||
	cmd := util.PrependProxyEnv("unattended-upgrades --dry-run -d 2>&1 ")
 | 
			
		||||
	release, err := strconv.ParseFloat(o.Release, 64)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return packNames, fmt.Errorf(
 | 
			
		||||
			"OS Release Version is invalid, %s, %s", o.Family, o.Release)
 | 
			
		||||
	}
 | 
			
		||||
	switch {
 | 
			
		||||
	case release < 12:
 | 
			
		||||
		return packNames, fmt.Errorf(
 | 
			
		||||
			"Support expired. %s, %s", o.Family, o.Release)
 | 
			
		||||
 | 
			
		||||
	case 12 < release && release < 14:
 | 
			
		||||
		cmd += `| grep 'pkgs that look like they should be upgraded:' |
 | 
			
		||||
			sed -e 's/pkgs that look like they should be upgraded://g'`
 | 
			
		||||
 | 
			
		||||
	case 14 < release:
 | 
			
		||||
		cmd += `| grep 'Packages that will be upgraded:' |
 | 
			
		||||
			sed -e 's/Packages that will be upgraded://g'`
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		return packNames, fmt.Errorf(
 | 
			
		||||
			"Not supported yet. %s, %s", o.Family, o.Release)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := o.ssh(cmd, sudo)
 | 
			
		||||
	if r.isSuccess(0, 1) {
 | 
			
		||||
		packNames = strings.Split(strings.TrimSpace(r.Stdout), " ")
 | 
			
		||||
		return packNames, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return packNames, fmt.Errorf(
 | 
			
		||||
		"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
		cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) GetUpgradablePackNames() (packNames []string, err error) {
 | 
			
		||||
	cmd := util.PrependProxyEnv("apt-get upgrade --dry-run")
 | 
			
		||||
	r := o.ssh(cmd, sudo)
 | 
			
		||||
	if r.isSuccess(0, 1) {
 | 
			
		||||
		return o.parseAptGetUpgrade(r.Stdout)
 | 
			
		||||
	}
 | 
			
		||||
	return packNames, fmt.Errorf(
 | 
			
		||||
		"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
		cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) parseAptGetUpgrade(stdout string) (upgradableNames []string, err error) {
 | 
			
		||||
	startRe, _ := regexp.Compile(`The following packages will be upgraded:`)
 | 
			
		||||
	stopRe, _ := regexp.Compile(`^(\d+) upgraded.*`)
 | 
			
		||||
	startLineFound, stopLineFound := false, false
 | 
			
		||||
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
	for _, line := range lines {
 | 
			
		||||
		if !startLineFound {
 | 
			
		||||
			if matche := startRe.MatchString(line); matche {
 | 
			
		||||
				startLineFound = true
 | 
			
		||||
			}
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		result := stopRe.FindStringSubmatch(line)
 | 
			
		||||
		if len(result) == 2 {
 | 
			
		||||
			numUpgradablePacks, err := strconv.Atoi(result[1])
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, fmt.Errorf(
 | 
			
		||||
					"Failed to scan upgradable packages number. line: %s", line)
 | 
			
		||||
			}
 | 
			
		||||
			if numUpgradablePacks != len(upgradableNames) {
 | 
			
		||||
				return nil, fmt.Errorf(
 | 
			
		||||
					"Failed to scan upgradable packages, expected: %s, detected: %d",
 | 
			
		||||
					result[1], len(upgradableNames))
 | 
			
		||||
			}
 | 
			
		||||
			stopLineFound = true
 | 
			
		||||
			o.log.Debugf("Found the stop line. line: %s", line)
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		upgradableNames = append(upgradableNames, strings.Fields(line)...)
 | 
			
		||||
	}
 | 
			
		||||
	if !startLineFound {
 | 
			
		||||
		// no upgrades
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if !stopLineFound {
 | 
			
		||||
		// There are upgrades, but not found the stop line.
 | 
			
		||||
		return nil, fmt.Errorf("Failed to scan upgradable packages")
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePacksList CvePacksList, err error) {
 | 
			
		||||
 | 
			
		||||
	// { CVE ID: [packageInfo] }
 | 
			
		||||
	cvePackages := make(map[string][]models.PackageInfo)
 | 
			
		||||
 | 
			
		||||
	type strarray []string
 | 
			
		||||
	resChan := make(chan struct {
 | 
			
		||||
		models.PackageInfo
 | 
			
		||||
		strarray
 | 
			
		||||
	}, len(unsecurePacks))
 | 
			
		||||
	errChan := make(chan error, len(unsecurePacks))
 | 
			
		||||
	reqChan := make(chan models.PackageInfo, len(unsecurePacks))
 | 
			
		||||
	defer close(resChan)
 | 
			
		||||
	defer close(errChan)
 | 
			
		||||
	defer close(reqChan)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		for _, pack := range unsecurePacks {
 | 
			
		||||
			reqChan <- pack
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	timeout := time.After(30 * 60 * time.Second)
 | 
			
		||||
 | 
			
		||||
	concurrency := 10
 | 
			
		||||
	tasks := util.GenWorkers(concurrency)
 | 
			
		||||
	for range unsecurePacks {
 | 
			
		||||
		tasks <- func() {
 | 
			
		||||
			select {
 | 
			
		||||
			case pack := <-reqChan:
 | 
			
		||||
				func(p models.PackageInfo) {
 | 
			
		||||
					if cveIds, err := o.scanPackageCveIds(p); err != nil {
 | 
			
		||||
						errChan <- err
 | 
			
		||||
					} else {
 | 
			
		||||
						resChan <- struct {
 | 
			
		||||
							models.PackageInfo
 | 
			
		||||
							strarray
 | 
			
		||||
						}{p, cveIds}
 | 
			
		||||
					}
 | 
			
		||||
				}(pack)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := 0; i < len(unsecurePacks); i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case pair := <-resChan:
 | 
			
		||||
			pack := pair.PackageInfo
 | 
			
		||||
			cveIds := pair.strarray
 | 
			
		||||
			for _, cveID := range cveIds {
 | 
			
		||||
				cvePackages[cveID] = appendPackIfMissing(cvePackages[cveID], pack)
 | 
			
		||||
			}
 | 
			
		||||
			o.log.Infof("(%d/%d) Scanned %s-%s : %s",
 | 
			
		||||
				i+1, len(unsecurePacks), pair.Name, pair.PackageInfo.Version, cveIds)
 | 
			
		||||
		case err := <-errChan:
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			return nil, fmt.Errorf("Timeout scanPackageCveIds.")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var cveIds []string
 | 
			
		||||
	for k := range cvePackages {
 | 
			
		||||
		cveIds = append(cveIds, k)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o.log.Debugf("%d Cves are found. cves: %v", len(cveIds), cveIds)
 | 
			
		||||
 | 
			
		||||
	o.log.Info("Fetching CVE details...")
 | 
			
		||||
	cveDetails, err := cveapi.CveClient.FetchCveDetails(cveIds)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Info("Done")
 | 
			
		||||
 | 
			
		||||
	for _, detail := range cveDetails {
 | 
			
		||||
		cvePacksList = append(cvePacksList, CvePacksInfo{
 | 
			
		||||
			CveID:     detail.CveID,
 | 
			
		||||
			CveDetail: detail,
 | 
			
		||||
			Packs:     cvePackages[detail.CveID],
 | 
			
		||||
			//  CvssScore: cinfo.CvssScore(conf.Lang),
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	sort.Sort(CvePacksList(cvePacksList))
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) scanPackageCveIds(pack models.PackageInfo) (cveIds []string, err error) {
 | 
			
		||||
	cmd := ""
 | 
			
		||||
	switch o.Family {
 | 
			
		||||
	case "ubuntu":
 | 
			
		||||
		cmd = fmt.Sprintf(`apt-get changelog %s | grep '\(urgency\|CVE\)'`, pack.Name)
 | 
			
		||||
	case "debian":
 | 
			
		||||
		cmd = fmt.Sprintf(`aptitude changelog %s | grep '\(urgency\|CVE\)'`, pack.Name)
 | 
			
		||||
	}
 | 
			
		||||
	cmd = util.PrependProxyEnv(cmd)
 | 
			
		||||
 | 
			
		||||
	r := o.ssh(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		o.log.Warnf(
 | 
			
		||||
			"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
			cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
		// Ignore this Error.
 | 
			
		||||
		return nil, nil
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	cveIds, err = o.getCveIDParsingChangelog(r.Stdout, pack.Name, pack.Version)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		trimUbuntu := strings.Split(pack.Version, "ubuntu")[0]
 | 
			
		||||
		return o.getCveIDParsingChangelog(r.Stdout, pack.Name, trimUbuntu)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) getCveIDParsingChangelog(changelog string,
 | 
			
		||||
	packName string, versionOrLater string) (cveIDs []string, err error) {
 | 
			
		||||
 | 
			
		||||
	cveIDs, err = o.parseChangelog(changelog, packName, versionOrLater)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ver := strings.Split(versionOrLater, "ubuntu")[0]
 | 
			
		||||
	cveIDs, err = o.parseChangelog(changelog, packName, ver)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	splittedByColon := strings.Split(versionOrLater, ":")
 | 
			
		||||
	if 1 < len(splittedByColon) {
 | 
			
		||||
		ver = splittedByColon[1]
 | 
			
		||||
	}
 | 
			
		||||
	cveIDs, err = o.parseChangelog(changelog, packName, ver)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//TODO report as unable to parse changelog.
 | 
			
		||||
	o.log.Warn(err)
 | 
			
		||||
	return []string{}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Collect CVE-IDs included in the changelog.
 | 
			
		||||
// The version which specified in argument(versionOrLater) is excluded.
 | 
			
		||||
func (o *debian) parseChangelog(changelog string,
 | 
			
		||||
	packName string, versionOrLater string) (cveIDs []string, err error) {
 | 
			
		||||
 | 
			
		||||
	cveRe, _ := regexp.Compile(`(CVE-\d{4}-\d{4})`)
 | 
			
		||||
	stopRe, _ := regexp.Compile(fmt.Sprintf(`\(%s\)`, regexp.QuoteMeta(versionOrLater)))
 | 
			
		||||
	stopLineFound := false
 | 
			
		||||
	lines := strings.Split(changelog, "\n")
 | 
			
		||||
	for _, line := range lines {
 | 
			
		||||
		if matche := stopRe.MatchString(line); matche {
 | 
			
		||||
			o.log.Debugf("Found the stop line. line: %s", line)
 | 
			
		||||
			stopLineFound = true
 | 
			
		||||
			break
 | 
			
		||||
		} else if matches := cveRe.FindAllString(line, -1); len(matches) > 0 {
 | 
			
		||||
			for _, m := range matches {
 | 
			
		||||
				cveIDs = util.AppendIfMissing(cveIDs, m)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !stopLineFound {
 | 
			
		||||
		return []string{}, fmt.Errorf(
 | 
			
		||||
			"Failed to scan CVE IDs. The version is not in changelog. name: %s, version: %s",
 | 
			
		||||
			packName,
 | 
			
		||||
			versionOrLater,
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type packCandidateVer struct {
 | 
			
		||||
	Name      string
 | 
			
		||||
	Installed string
 | 
			
		||||
	Candidate string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseAptCachePolicy the stdout of parse pat-get cache policy
 | 
			
		||||
func (o *debian) parseAptCachePolicy(stdout, name string) (packCandidateVer, error) {
 | 
			
		||||
	ver := packCandidateVer{Name: name}
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
	for _, line := range lines {
 | 
			
		||||
		fields := strings.Fields(line)
 | 
			
		||||
		if len(fields) != 2 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		switch fields[0] {
 | 
			
		||||
		case "Installed:":
 | 
			
		||||
			ver.Installed = fields[1]
 | 
			
		||||
		case "Candidate:":
 | 
			
		||||
			ver.Candidate = fields[1]
 | 
			
		||||
			return ver, nil
 | 
			
		||||
		default:
 | 
			
		||||
			// nop
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return ver, fmt.Errorf("Unknown Format: %s", stdout)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func appendPackIfMissing(slice []models.PackageInfo, s models.PackageInfo) []models.PackageInfo {
 | 
			
		||||
	for _, ele := range slice {
 | 
			
		||||
		if ele.Name == s.Name &&
 | 
			
		||||
			ele.Version == s.Version &&
 | 
			
		||||
			ele.Release == s.Release {
 | 
			
		||||
			return slice
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return append(slice, s)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										601
									
								
								scan/debian_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										601
									
								
								scan/debian_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,601 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestParseScanedPackagesLineDebian(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	var packagetests = []struct {
 | 
			
		||||
		in      string
 | 
			
		||||
		name    string
 | 
			
		||||
		version string
 | 
			
		||||
	}{
 | 
			
		||||
		{"base-passwd	3.5.33", "base-passwd", "3.5.33"},
 | 
			
		||||
		{"bzip2	1.0.6-5", "bzip2", "1.0.6-5"},
 | 
			
		||||
		{"adduser	3.113+nmu3ubuntu3", "adduser", "3.113+nmu3ubuntu3"},
 | 
			
		||||
		{"bash	4.3-7ubuntu1.5", "bash", "4.3-7ubuntu1.5"},
 | 
			
		||||
		{"bsdutils	1:2.20.1-5.1ubuntu20.4", "bsdutils", "1:2.20.1-5.1ubuntu20.4"},
 | 
			
		||||
		{"ca-certificates	20141019ubuntu0.14.04.1", "ca-certificates", "20141019ubuntu0.14.04.1"},
 | 
			
		||||
		{"apt	1.0.1ubuntu2.8", "apt", "1.0.1ubuntu2.8"},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newDebian(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range packagetests {
 | 
			
		||||
		n, v, _ := d.parseScanedPackagesLine(tt.in)
 | 
			
		||||
		if n != tt.name {
 | 
			
		||||
			t.Errorf("name: expected %s, actual %s", tt.name, n)
 | 
			
		||||
		}
 | 
			
		||||
		if v != tt.version {
 | 
			
		||||
			t.Errorf("version: expected %s, actual %s", tt.version, v)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestgetCveIDParsingChangelog(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       []string
 | 
			
		||||
		expected []string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			// verubuntu1
 | 
			
		||||
			[]string{
 | 
			
		||||
				"systemd",
 | 
			
		||||
				"228-4ubuntu1",
 | 
			
		||||
				`systemd (229-2) unstable; urgency=medium
 | 
			
		||||
systemd (229-1) unstable; urgency=medium
 | 
			
		||||
systemd (228-6) unstable; urgency=medium
 | 
			
		||||
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
 | 
			
		||||
systemd (228-5) unstable; urgency=medium
 | 
			
		||||
systemd (228-4) unstable; urgency=medium
 | 
			
		||||
systemd (228-3) unstable; urgency=medium
 | 
			
		||||
systemd (228-2) unstable; urgency=medium
 | 
			
		||||
systemd (228-1) unstable; urgency=medium
 | 
			
		||||
systemd (227-3) unstable; urgency=medium
 | 
			
		||||
systemd (227-2) unstable; urgency=medium
 | 
			
		||||
systemd (227-1) unstable; urgency=medium`,
 | 
			
		||||
			},
 | 
			
		||||
			[]string{
 | 
			
		||||
				"CVE-2015-2325",
 | 
			
		||||
				"CVE-2015-2326",
 | 
			
		||||
				"CVE-2015-3210",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			// ver
 | 
			
		||||
			[]string{
 | 
			
		||||
				"libpcre3",
 | 
			
		||||
				"2:8.38-1ubuntu1",
 | 
			
		||||
				`pcre3 (2:8.38-2) unstable; urgency=low
 | 
			
		||||
pcre3 (2:8.38-1) unstable; urgency=low
 | 
			
		||||
pcre3 (2:8.35-8) unstable; urgency=low
 | 
			
		||||
pcre3 (2:8.35-7.4) unstable; urgency=medium
 | 
			
		||||
pcre3 (2:8.35-7.3) unstable; urgency=medium
 | 
			
		||||
pcre3 (2:8.35-7.2) unstable; urgency=low
 | 
			
		||||
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
 | 
			
		||||
pcre3 (2:8.35-7.1) unstable; urgency=medium
 | 
			
		||||
pcre3 (2:8.35-7) unstable; urgency=medium`,
 | 
			
		||||
			},
 | 
			
		||||
			[]string{
 | 
			
		||||
				"CVE-2015-2325",
 | 
			
		||||
				"CVE-2015-2326",
 | 
			
		||||
				"CVE-2015-3210",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			// ver-ubuntu3
 | 
			
		||||
			[]string{
 | 
			
		||||
				"sysvinit",
 | 
			
		||||
				"2.88dsf-59.2ubuntu3",
 | 
			
		||||
				`sysvinit (2.88dsf-59.3ubuntu1) xenial; urgency=low
 | 
			
		||||
sysvinit (2.88dsf-59.3) unstable; urgency=medium
 | 
			
		||||
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
 | 
			
		||||
sysvinit (2.88dsf-59.2ubuntu3) xenial; urgency=medium
 | 
			
		||||
sysvinit (2.88dsf-59.2ubuntu2) wily; urgency=medium
 | 
			
		||||
sysvinit (2.88dsf-59.2ubuntu1) wily; urgency=medium
 | 
			
		||||
CVE-2015-2321: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
sysvinit (2.88dsf-59.2) unstable; urgency=medium
 | 
			
		||||
sysvinit (2.88dsf-59.1ubuntu3) wily; urgency=medium
 | 
			
		||||
CVE-2015-2322: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
sysvinit (2.88dsf-59.1ubuntu2) wily; urgency=medium
 | 
			
		||||
sysvinit (2.88dsf-59.1ubuntu1) wily; urgency=medium
 | 
			
		||||
sysvinit (2.88dsf-59.1) unstable; urgency=medium
 | 
			
		||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
sysvinit (2.88dsf-59) unstable; urgency=medium
 | 
			
		||||
sysvinit (2.88dsf-58) unstable; urgency=low
 | 
			
		||||
sysvinit (2.88dsf-57) unstable; urgency=low`,
 | 
			
		||||
			},
 | 
			
		||||
			[]string{
 | 
			
		||||
				"CVE-2015-2325",
 | 
			
		||||
				"CVE-2015-2326",
 | 
			
		||||
				"CVE-2015-3210",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			// 1:ver-ubuntu3
 | 
			
		||||
			[]string{
 | 
			
		||||
				"bsdutils",
 | 
			
		||||
				"1:2.27.1-1ubuntu3",
 | 
			
		||||
				` util-linux (2.27.1-3ubuntu1) xenial; urgency=medium
 | 
			
		||||
util-linux (2.27.1-3) unstable; urgency=medium
 | 
			
		||||
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
 | 
			
		||||
util-linux (2.27.1-2) unstable; urgency=medium
 | 
			
		||||
util-linux (2.27.1-1ubuntu4) xenial; urgency=medium
 | 
			
		||||
util-linux (2.27.1-1ubuntu3) xenial; urgency=medium
 | 
			
		||||
util-linux (2.27.1-1ubuntu2) xenial; urgency=medium
 | 
			
		||||
util-linux (2.27.1-1ubuntu1) xenial; urgency=medium
 | 
			
		||||
util-linux (2.27.1-1) unstable; urgency=medium
 | 
			
		||||
util-linux (2.27-3ubuntu1) xenial; urgency=medium
 | 
			
		||||
util-linux (2.27-3) unstable; urgency=medium
 | 
			
		||||
util-linux (2.27-2) unstable; urgency=medium
 | 
			
		||||
util-linux (2.27-1) unstable; urgency=medium
 | 
			
		||||
util-linux (2.27~rc2-2) experimental; urgency=medium
 | 
			
		||||
util-linux (2.27~rc2-1) experimental; urgency=medium
 | 
			
		||||
util-linux (2.27~rc1-1) experimental; urgency=medium
 | 
			
		||||
util-linux (2.26.2-9) unstable; urgency=medium
 | 
			
		||||
util-linux (2.26.2-8) experimental; urgency=medium
 | 
			
		||||
util-linux (2.26.2-7) experimental; urgency=medium
 | 
			
		||||
util-linux (2.26.2-6ubuntu3) wily; urgency=medium
 | 
			
		||||
CVE-2015-2329: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
util-linux (2.26.2-6ubuntu2) wily; urgency=medium
 | 
			
		||||
util-linux (2.26.2-6ubuntu1) wily; urgency=medium
 | 
			
		||||
util-linux (2.26.2-6) unstable; urgency=medium`,
 | 
			
		||||
			},
 | 
			
		||||
			[]string{
 | 
			
		||||
				"CVE-2015-2325",
 | 
			
		||||
				"CVE-2015-2326",
 | 
			
		||||
				"CVE-2015-3210",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newDebian(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual, _ := d.getCveIDParsingChangelog(tt.in[2], tt.in[0], tt.in[1])
 | 
			
		||||
		if len(actual) != len(tt.expected) {
 | 
			
		||||
			t.Errorf("Len of return array are'nt same. expected %#v, actual %#v", tt.expected, actual)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		for i := range tt.expected {
 | 
			
		||||
			if actual[i] != tt.expected[i] {
 | 
			
		||||
				t.Errorf("expected %s, actual %s", tt.expected[i], actual[i])
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		_, err := d.getCveIDParsingChangelog(tt.in[2], tt.in[0], "version number do'nt match case")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Returning error is unexpected.")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetUpdatablePackNames(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       string
 | 
			
		||||
		expected []string
 | 
			
		||||
	}{
 | 
			
		||||
		{ // Ubuntu 12.04
 | 
			
		||||
			`Reading package lists... Done
 | 
			
		||||
Building dependency tree
 | 
			
		||||
Reading state information... Done
 | 
			
		||||
The following packages will be upgraded:
 | 
			
		||||
  apt ca-certificates cpio dpkg e2fslibs e2fsprogs gnupg gpgv libc-bin libc6 libcomerr2 libpcre3
 | 
			
		||||
  libpng12-0 libss2 libssl1.0.0 libudev0 multiarch-support openssl tzdata udev upstart
 | 
			
		||||
21 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
 | 
			
		||||
Inst dpkg [1.16.1.2ubuntu7.5] (1.16.1.2ubuntu7.7 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf dpkg (1.16.1.2ubuntu7.7 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst upstart [1.5-0ubuntu7.2] (1.5-0ubuntu7.3 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst libc-bin [2.15-0ubuntu10.10] (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64]) [libc6:amd64 ]
 | 
			
		||||
Conf libc-bin (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64]) [libc6:amd64 ]
 | 
			
		||||
Inst libc6 [2.15-0ubuntu10.10] (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf libc6 (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst libudev0 [175-0ubuntu9.9] (175-0ubuntu9.10 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst tzdata [2015a-0ubuntu0.12.04] (2015g-0ubuntu0.12.04 Ubuntu:12.04/precise-updates [all])
 | 
			
		||||
Conf tzdata (2015g-0ubuntu0.12.04 Ubuntu:12.04/precise-updates [all])
 | 
			
		||||
Inst e2fslibs [1.42-1ubuntu2] (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64]) [e2fsprogs:amd64 on e2fslibs:amd64] [e2fsprogs:amd64 ]
 | 
			
		||||
Conf e2fslibs (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64]) [e2fsprogs:amd64 ]
 | 
			
		||||
Inst e2fsprogs [1.42-1ubuntu2] (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf e2fsprogs (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst gpgv [1.4.11-3ubuntu2.7] (1.4.11-3ubuntu2.9 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf gpgv (1.4.11-3ubuntu2.9 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst gnupg [1.4.11-3ubuntu2.7] (1.4.11-3ubuntu2.9 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf gnupg (1.4.11-3ubuntu2.9 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst apt [0.8.16~exp12ubuntu10.22] (0.8.16~exp12ubuntu10.26 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf apt (0.8.16~exp12ubuntu10.26 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst libcomerr2 [1.42-1ubuntu2] (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf libcomerr2 (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst libss2 [1.42-1ubuntu2] (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf libss2 (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst libssl1.0.0 [1.0.1-4ubuntu5.21] (1.0.1-4ubuntu5.34 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf libssl1.0.0 (1.0.1-4ubuntu5.34 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst libpcre3 [8.12-4] (8.12-4ubuntu0.1 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst libpng12-0 [1.2.46-3ubuntu4] (1.2.46-3ubuntu4.2 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst multiarch-support [2.15-0ubuntu10.10] (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf multiarch-support (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst cpio [2.11-7ubuntu3.1] (2.11-7ubuntu3.2 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst udev [175-0ubuntu9.9] (175-0ubuntu9.10 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst openssl [1.0.1-4ubuntu5.33] (1.0.1-4ubuntu5.34 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst ca-certificates [20141019ubuntu0.12.04.1] (20160104ubuntu0.12.04.1 Ubuntu:12.04/precise-updates [all])
 | 
			
		||||
Conf libudev0 (175-0ubuntu9.10 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf upstart (1.5-0ubuntu7.3 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf libpcre3 (8.12-4ubuntu0.1 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf libpng12-0 (1.2.46-3ubuntu4.2 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf cpio (2.11-7ubuntu3.2 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf udev (175-0ubuntu9.10 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf openssl (1.0.1-4ubuntu5.34 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf ca-certificates (20160104ubuntu0.12.04.1 Ubuntu:12.04/precise-updates [all])`,
 | 
			
		||||
			[]string{
 | 
			
		||||
				"apt",
 | 
			
		||||
				"ca-certificates",
 | 
			
		||||
				"cpio",
 | 
			
		||||
				"dpkg",
 | 
			
		||||
				"e2fslibs",
 | 
			
		||||
				"e2fsprogs",
 | 
			
		||||
				"gnupg",
 | 
			
		||||
				"gpgv",
 | 
			
		||||
				"libc-bin",
 | 
			
		||||
				"libc6",
 | 
			
		||||
				"libcomerr2",
 | 
			
		||||
				"libpcre3",
 | 
			
		||||
				"libpng12-0",
 | 
			
		||||
				"libss2",
 | 
			
		||||
				"libssl1.0.0",
 | 
			
		||||
				"libudev0",
 | 
			
		||||
				"multiarch-support",
 | 
			
		||||
				"openssl",
 | 
			
		||||
				"tzdata",
 | 
			
		||||
				"udev",
 | 
			
		||||
				"upstart",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{ // Ubuntu 14.04
 | 
			
		||||
			`Reading package lists... Done
 | 
			
		||||
Building dependency tree
 | 
			
		||||
Reading state information... Done
 | 
			
		||||
Calculating upgrade... Done
 | 
			
		||||
The following packages will be upgraded:
 | 
			
		||||
  apt apt-utils base-files bsdutils coreutils cpio dh-python dpkg e2fslibs
 | 
			
		||||
  e2fsprogs gcc-4.8-base gcc-4.9-base gnupg gpgv ifupdown initscripts iproute2
 | 
			
		||||
  isc-dhcp-client isc-dhcp-common libapt-inst1.5 libapt-pkg4.12 libblkid1
 | 
			
		||||
  libc-bin libc6 libcgmanager0 libcomerr2 libdrm2 libexpat1 libffi6 libgcc1
 | 
			
		||||
  libgcrypt11 libgnutls-openssl27 libgnutls26 libmount1 libpcre3 libpng12-0
 | 
			
		||||
  libpython3.4-minimal libpython3.4-stdlib libsqlite3-0 libss2 libssl1.0.0
 | 
			
		||||
  libstdc++6 libtasn1-6 libudev1 libuuid1 login mount multiarch-support
 | 
			
		||||
  ntpdate passwd python3.4 python3.4-minimal rsyslog sudo sysv-rc
 | 
			
		||||
  sysvinit-utils tzdata udev util-linux
 | 
			
		||||
59 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
 | 
			
		||||
Inst base-files [7.2ubuntu5.2] (7.2ubuntu5.4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf base-files (7.2ubuntu5.4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst coreutils [8.21-1ubuntu5.1] (8.21-1ubuntu5.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf coreutils (8.21-1ubuntu5.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst dpkg [1.17.5ubuntu5.3] (1.17.5ubuntu5.5 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf dpkg (1.17.5ubuntu5.5 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libc-bin [2.19-0ubuntu6.5] (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libc6 [2.19-0ubuntu6.5] (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libgcc1 [1:4.9.1-0ubuntu1] (1:4.9.3-0ubuntu4 Ubuntu:14.04/trusty-updates [amd64]) []
 | 
			
		||||
Inst gcc-4.9-base [4.9.1-0ubuntu1] (4.9.3-0ubuntu4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf gcc-4.9-base (4.9.3-0ubuntu4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libgcc1 (1:4.9.3-0ubuntu4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libc6 (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libc-bin (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst e2fslibs [1.42.9-3ubuntu1] (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64]) [e2fsprogs:amd64 on e2fslibs:amd64] [e2fsprogs:amd64 ]
 | 
			
		||||
Conf e2fslibs (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64]) [e2fsprogs:amd64 ]
 | 
			
		||||
Inst e2fsprogs [1.42.9-3ubuntu1] (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf e2fsprogs (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst login [1:4.1.5.1-1ubuntu9] (1:4.1.5.1-1ubuntu9.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf login (1:4.1.5.1-1ubuntu9.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst mount [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf mount (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst tzdata [2015a-0ubuntu0.14.04] (2015g-0ubuntu0.14.04 Ubuntu:14.04/trusty-updates [all])
 | 
			
		||||
Conf tzdata (2015g-0ubuntu0.14.04 Ubuntu:14.04/trusty-updates [all])
 | 
			
		||||
Inst sysvinit-utils [2.88dsf-41ubuntu6] (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst sysv-rc [2.88dsf-41ubuntu6] (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [all])
 | 
			
		||||
Conf sysv-rc (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [all])
 | 
			
		||||
Conf sysvinit-utils (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst util-linux [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf util-linux (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst gcc-4.8-base [4.8.2-19ubuntu1] (4.8.4-2ubuntu1~14.04.1 Ubuntu:14.04/trusty-updates [amd64]) [libstdc++6:amd64 ]
 | 
			
		||||
Conf gcc-4.8-base (4.8.4-2ubuntu1~14.04.1 Ubuntu:14.04/trusty-updates [amd64]) [libstdc++6:amd64 ]
 | 
			
		||||
Inst libstdc++6 [4.8.2-19ubuntu1] (4.8.4-2ubuntu1~14.04.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libstdc++6 (4.8.4-2ubuntu1~14.04.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libapt-pkg4.12 [1.0.1ubuntu2.6] (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libapt-pkg4.12 (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst gpgv [1.4.16-1ubuntu2.1] (1.4.16-1ubuntu2.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf gpgv (1.4.16-1ubuntu2.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst gnupg [1.4.16-1ubuntu2.1] (1.4.16-1ubuntu2.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf gnupg (1.4.16-1ubuntu2.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst apt [1.0.1ubuntu2.6] (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf apt (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst bsdutils [1:2.20.1-5.1ubuntu20.4] (1:2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf bsdutils (1:2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst passwd [1:4.1.5.1-1ubuntu9] (1:4.1.5.1-1ubuntu9.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf passwd (1:4.1.5.1-1ubuntu9.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libuuid1 [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libuuid1 (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libblkid1 [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libblkid1 (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libcomerr2 [1.42.9-3ubuntu1] (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libcomerr2 (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libmount1 [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libmount1 (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libpcre3 [1:8.31-2ubuntu2] (1:8.31-2ubuntu2.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libpcre3 (1:8.31-2ubuntu2.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libss2 [1.42.9-3ubuntu1] (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libss2 (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libapt-inst1.5 [1.0.1ubuntu2.6] (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libexpat1 [2.1.0-4ubuntu1] (2.1.0-4ubuntu1.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libffi6 [3.1~rc1+r3.0.13-12] (3.1~rc1+r3.0.13-12ubuntu0.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libgcrypt11 [1.5.3-2ubuntu4.1] (1.5.3-2ubuntu4.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libtasn1-6 [3.4-3ubuntu0.1] (3.4-3ubuntu0.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libgnutls-openssl27 [2.12.23-12ubuntu2.1] (2.12.23-12ubuntu2.4 Ubuntu:14.04/trusty-updates [amd64]) []
 | 
			
		||||
Inst libgnutls26 [2.12.23-12ubuntu2.1] (2.12.23-12ubuntu2.4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libsqlite3-0 [3.8.2-1ubuntu2] (3.8.2-1ubuntu2.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst python3.4 [3.4.0-2ubuntu1] (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64]) []
 | 
			
		||||
Inst libpython3.4-stdlib [3.4.0-2ubuntu1] (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64]) []
 | 
			
		||||
Inst python3.4-minimal [3.4.0-2ubuntu1] (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64]) []
 | 
			
		||||
Inst libssl1.0.0 [1.0.1f-1ubuntu2.8] (1.0.1f-1ubuntu2.16 Ubuntu:14.04/trusty-updates [amd64]) []
 | 
			
		||||
Inst libpython3.4-minimal [3.4.0-2ubuntu1] (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst ntpdate [1:4.2.6.p5+dfsg-3ubuntu2.14.04.2] (1:4.2.6.p5+dfsg-3ubuntu2.14.04.8 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libdrm2 [2.4.56-1~ubuntu2] (2.4.64-1~ubuntu14.04.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libpng12-0 [1.2.50-1ubuntu2] (1.2.50-1ubuntu2.14.04.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst initscripts [2.88dsf-41ubuntu6] (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libcgmanager0 [0.24-0ubuntu7.3] (0.24-0ubuntu7.5 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst udev [204-5ubuntu20.10] (204-5ubuntu20.18 Ubuntu:14.04/trusty-updates [amd64]) []
 | 
			
		||||
Inst libudev1 [204-5ubuntu20.10] (204-5ubuntu20.18 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst multiarch-support [2.19-0ubuntu6.5] (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf multiarch-support (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst apt-utils [1.0.1ubuntu2.6] (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst dh-python [1.20140128-1ubuntu8] (1.20140128-1ubuntu8.2 Ubuntu:14.04/trusty-updates [all])
 | 
			
		||||
Inst iproute2 [3.12.0-2] (3.12.0-2ubuntu1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst ifupdown [0.7.47.2ubuntu4.1] (0.7.47.2ubuntu4.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst isc-dhcp-client [4.2.4-7ubuntu12] (4.2.4-7ubuntu12.4 Ubuntu:14.04/trusty-updates [amd64]) []
 | 
			
		||||
Inst isc-dhcp-common [4.2.4-7ubuntu12] (4.2.4-7ubuntu12.4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst rsyslog [7.4.4-1ubuntu2.5] (7.4.4-1ubuntu2.6 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst sudo [1.8.9p5-1ubuntu1] (1.8.9p5-1ubuntu1.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst cpio [2.11+dfsg-1ubuntu1.1] (2.11+dfsg-1ubuntu1.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libapt-inst1.5 (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libexpat1 (2.1.0-4ubuntu1.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libffi6 (3.1~rc1+r3.0.13-12ubuntu0.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libgcrypt11 (1.5.3-2ubuntu4.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libtasn1-6 (3.4-3ubuntu0.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libgnutls26 (2.12.23-12ubuntu2.4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libgnutls-openssl27 (2.12.23-12ubuntu2.4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libsqlite3-0 (3.8.2-1ubuntu2.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libssl1.0.0 (1.0.1f-1ubuntu2.16 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libpython3.4-minimal (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf python3.4-minimal (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libpython3.4-stdlib (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf python3.4 (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf ntpdate (1:4.2.6.p5+dfsg-3ubuntu2.14.04.8 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libdrm2 (2.4.64-1~ubuntu14.04.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libpng12-0 (1.2.50-1ubuntu2.14.04.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf initscripts (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libcgmanager0 (0.24-0ubuntu7.5 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libudev1 (204-5ubuntu20.18 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf udev (204-5ubuntu20.18 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf apt-utils (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf dh-python (1.20140128-1ubuntu8.2 Ubuntu:14.04/trusty-updates [all])
 | 
			
		||||
Conf iproute2 (3.12.0-2ubuntu1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf ifupdown (0.7.47.2ubuntu4.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf isc-dhcp-common (4.2.4-7ubuntu12.4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf isc-dhcp-client (4.2.4-7ubuntu12.4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf rsyslog (7.4.4-1ubuntu2.6 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf sudo (1.8.9p5-1ubuntu1.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf cpio (2.11+dfsg-1ubuntu1.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
`,
 | 
			
		||||
			[]string{
 | 
			
		||||
				"apt",
 | 
			
		||||
				"apt-utils",
 | 
			
		||||
				"base-files",
 | 
			
		||||
				"bsdutils",
 | 
			
		||||
				"coreutils",
 | 
			
		||||
				"cpio",
 | 
			
		||||
				"dh-python",
 | 
			
		||||
				"dpkg",
 | 
			
		||||
				"e2fslibs",
 | 
			
		||||
				"e2fsprogs",
 | 
			
		||||
				"gcc-4.8-base",
 | 
			
		||||
				"gcc-4.9-base",
 | 
			
		||||
				"gnupg",
 | 
			
		||||
				"gpgv",
 | 
			
		||||
				"ifupdown",
 | 
			
		||||
				"initscripts",
 | 
			
		||||
				"iproute2",
 | 
			
		||||
				"isc-dhcp-client",
 | 
			
		||||
				"isc-dhcp-common",
 | 
			
		||||
				"libapt-inst1.5",
 | 
			
		||||
				"libapt-pkg4.12",
 | 
			
		||||
				"libblkid1",
 | 
			
		||||
				"libc-bin",
 | 
			
		||||
				"libc6",
 | 
			
		||||
				"libcgmanager0",
 | 
			
		||||
				"libcomerr2",
 | 
			
		||||
				"libdrm2",
 | 
			
		||||
				"libexpat1",
 | 
			
		||||
				"libffi6",
 | 
			
		||||
				"libgcc1",
 | 
			
		||||
				"libgcrypt11",
 | 
			
		||||
				"libgnutls-openssl27",
 | 
			
		||||
				"libgnutls26",
 | 
			
		||||
				"libmount1",
 | 
			
		||||
				"libpcre3",
 | 
			
		||||
				"libpng12-0",
 | 
			
		||||
				"libpython3.4-minimal",
 | 
			
		||||
				"libpython3.4-stdlib",
 | 
			
		||||
				"libsqlite3-0",
 | 
			
		||||
				"libss2",
 | 
			
		||||
				"libssl1.0.0",
 | 
			
		||||
				"libstdc++6",
 | 
			
		||||
				"libtasn1-6",
 | 
			
		||||
				"libudev1",
 | 
			
		||||
				"libuuid1",
 | 
			
		||||
				"login",
 | 
			
		||||
				"mount",
 | 
			
		||||
				"multiarch-support",
 | 
			
		||||
				"ntpdate",
 | 
			
		||||
				"passwd",
 | 
			
		||||
				"python3.4",
 | 
			
		||||
				"python3.4-minimal",
 | 
			
		||||
				"rsyslog",
 | 
			
		||||
				"sudo",
 | 
			
		||||
				"sysv-rc",
 | 
			
		||||
				"sysvinit-utils",
 | 
			
		||||
				"tzdata",
 | 
			
		||||
				"udev",
 | 
			
		||||
				"util-linux",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			//Ubuntu12.04
 | 
			
		||||
			`Reading package lists... Done
 | 
			
		||||
Building dependency tree
 | 
			
		||||
Reading state information... Done
 | 
			
		||||
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.`,
 | 
			
		||||
			[]string{},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			//Ubuntu14.04
 | 
			
		||||
			`Reading package lists... Done
 | 
			
		||||
Building dependency tree
 | 
			
		||||
Reading state information... Done
 | 
			
		||||
Calculating upgrade... Done
 | 
			
		||||
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.`,
 | 
			
		||||
			[]string{},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newDebian(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual, err := d.parseAptGetUpgrade(tt.in)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Returning error is unexpected.")
 | 
			
		||||
		}
 | 
			
		||||
		if len(tt.expected) != len(actual) {
 | 
			
		||||
			t.Errorf("Result length is not as same as expected. expected: %d, actual: %d", len(tt.expected), len(actual))
 | 
			
		||||
			pp.Println(tt.expected)
 | 
			
		||||
			pp.Println(actual)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		for i := range tt.expected {
 | 
			
		||||
			if tt.expected[i] != actual[i] {
 | 
			
		||||
				t.Errorf("[%d] expected %s, actual %s", i, tt.expected[i], actual[i])
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseAptCachePolicy(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		stdout   string
 | 
			
		||||
		name     string
 | 
			
		||||
		expected packCandidateVer
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			// Ubuntu 16.04
 | 
			
		||||
			`openssl:
 | 
			
		||||
  Installed: 1.0.2f-2ubuntu1
 | 
			
		||||
  Candidate: 1.0.2g-1ubuntu2
 | 
			
		||||
  Version table:
 | 
			
		||||
     1.0.2g-1ubuntu2 500
 | 
			
		||||
        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages
 | 
			
		||||
 *** 1.0.2f-2ubuntu1 100
 | 
			
		||||
        100 /var/lib/dpkg/status`,
 | 
			
		||||
			"openssl",
 | 
			
		||||
			packCandidateVer{
 | 
			
		||||
				Name:      "openssl",
 | 
			
		||||
				Installed: "1.0.2f-2ubuntu1",
 | 
			
		||||
				Candidate: "1.0.2g-1ubuntu2",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			// Ubuntu 14.04
 | 
			
		||||
			`openssl:
 | 
			
		||||
  Installed: 1.0.1f-1ubuntu2.16
 | 
			
		||||
  Candidate: 1.0.1f-1ubuntu2.17
 | 
			
		||||
  Version table:
 | 
			
		||||
     1.0.1f-1ubuntu2.17 0
 | 
			
		||||
        500 http://archive.ubuntu.com/ubuntu/ trusty-updates/main amd64 Packages
 | 
			
		||||
        500 http://archive.ubuntu.com/ubuntu/ trusty-security/main amd64 Packages
 | 
			
		||||
 *** 1.0.1f-1ubuntu2.16 0
 | 
			
		||||
        100 /var/lib/dpkg/status
 | 
			
		||||
     1.0.1f-1ubuntu2 0
 | 
			
		||||
        500 http://archive.ubuntu.com/ubuntu/ trusty/main amd64 Packages`,
 | 
			
		||||
			"openssl",
 | 
			
		||||
			packCandidateVer{
 | 
			
		||||
				Name:      "openssl",
 | 
			
		||||
				Installed: "1.0.1f-1ubuntu2.16",
 | 
			
		||||
				Candidate: "1.0.1f-1ubuntu2.17",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			// Ubuntu 12.04
 | 
			
		||||
			`openssl:
 | 
			
		||||
  Installed: 1.0.1-4ubuntu5.33
 | 
			
		||||
  Candidate: 1.0.1-4ubuntu5.34
 | 
			
		||||
  Version table:
 | 
			
		||||
     1.0.1-4ubuntu5.34 0
 | 
			
		||||
        500 http://archive.ubuntu.com/ubuntu/ precise-updates/main amd64 Packages
 | 
			
		||||
        500 http://archive.ubuntu.com/ubuntu/ precise-security/main amd64 Packages
 | 
			
		||||
 *** 1.0.1-4ubuntu5.33 0
 | 
			
		||||
        100 /var/lib/dpkg/status
 | 
			
		||||
     1.0.1-4ubuntu3 0
 | 
			
		||||
        500 http://archive.ubuntu.com/ubuntu/ precise/main amd64 Packages`,
 | 
			
		||||
			"openssl",
 | 
			
		||||
			packCandidateVer{
 | 
			
		||||
				Name:      "openssl",
 | 
			
		||||
				Installed: "1.0.1-4ubuntu5.33",
 | 
			
		||||
				Candidate: "1.0.1-4ubuntu5.34",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newDebian(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual, err := d.parseAptCachePolicy(tt.stdout, tt.name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Error has occurred: %s, actual: %#v", err, actual)
 | 
			
		||||
		}
 | 
			
		||||
		if !reflect.DeepEqual(tt.expected, actual) {
 | 
			
		||||
			e := pp.Sprintf("%v", tt.expected)
 | 
			
		||||
			a := pp.Sprintf("%v", actual)
 | 
			
		||||
			t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										129
									
								
								scan/linux.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								scan/linux.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,129 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"sort"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/cveapi"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type linux struct {
 | 
			
		||||
	ServerInfo config.ServerInfo
 | 
			
		||||
 | 
			
		||||
	Family  string
 | 
			
		||||
	Release string
 | 
			
		||||
	osPackages
 | 
			
		||||
	log *logrus.Entry
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *linux) ssh(cmd string, sudo bool) sshResult {
 | 
			
		||||
	return sshExec(l.ServerInfo, cmd, sudo, l.log)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *linux) setServerInfo(c config.ServerInfo) {
 | 
			
		||||
	l.ServerInfo = c
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *linux) getServerInfo() config.ServerInfo {
 | 
			
		||||
	return l.ServerInfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *linux) setDistributionInfo(fam, rel string) {
 | 
			
		||||
	l.Family = fam
 | 
			
		||||
	l.Release = rel
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *linux) convertToModel() (models.ScanResult, error) {
 | 
			
		||||
	var cves, unknownScoreCves []models.CveInfo
 | 
			
		||||
	for _, p := range l.UnsecurePackages {
 | 
			
		||||
		if p.CveDetail.CvssScore(config.Conf.Lang) < 0 {
 | 
			
		||||
			unknownScoreCves = append(unknownScoreCves, models.CveInfo{
 | 
			
		||||
				CveDetail:        p.CveDetail,
 | 
			
		||||
				Packages:         p.Packs,
 | 
			
		||||
				DistroAdvisories: p.DistroAdvisories, // only Amazon Linux
 | 
			
		||||
			})
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cpenames := []models.CpeName{}
 | 
			
		||||
		for _, cpename := range p.CpeNames {
 | 
			
		||||
			cpenames = append(cpenames,
 | 
			
		||||
				models.CpeName{Name: cpename})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cve := models.CveInfo{
 | 
			
		||||
			CveDetail:        p.CveDetail,
 | 
			
		||||
			Packages:         p.Packs,
 | 
			
		||||
			DistroAdvisories: p.DistroAdvisories, // only Amazon Linux
 | 
			
		||||
			CpeNames:         cpenames,
 | 
			
		||||
		}
 | 
			
		||||
		cves = append(cves, cve)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return models.ScanResult{
 | 
			
		||||
		ServerName:  l.ServerInfo.ServerName,
 | 
			
		||||
		Family:      l.Family,
 | 
			
		||||
		Release:     l.Release,
 | 
			
		||||
		KnownCves:   cves,
 | 
			
		||||
		UnknownCves: unknownScoreCves,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// scanVulnByCpeName search vulnerabilities that specified in config file.
 | 
			
		||||
func (l *linux) scanVulnByCpeName() error {
 | 
			
		||||
	unsecurePacks := CvePacksList{}
 | 
			
		||||
 | 
			
		||||
	serverInfo := l.getServerInfo()
 | 
			
		||||
	cpeNames := serverInfo.CpeNames
 | 
			
		||||
 | 
			
		||||
	// remove duplicate
 | 
			
		||||
	set := map[string]CvePacksInfo{}
 | 
			
		||||
 | 
			
		||||
	for _, name := range cpeNames {
 | 
			
		||||
		details, err := cveapi.CveClient.FetchCveDetailsByCpeName(name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		for _, detail := range details {
 | 
			
		||||
			if val, ok := set[detail.CveID]; ok {
 | 
			
		||||
				names := val.CpeNames
 | 
			
		||||
				names = append(names, name)
 | 
			
		||||
				val.CpeNames = names
 | 
			
		||||
				set[detail.CveID] = val
 | 
			
		||||
			} else {
 | 
			
		||||
				set[detail.CveID] = CvePacksInfo{
 | 
			
		||||
					CveID:     detail.CveID,
 | 
			
		||||
					CveDetail: detail,
 | 
			
		||||
					CpeNames:  []string{name},
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for key := range set {
 | 
			
		||||
		unsecurePacks = append(unsecurePacks, set[key])
 | 
			
		||||
	}
 | 
			
		||||
	unsecurePacks = append(unsecurePacks, l.UnsecurePackages...)
 | 
			
		||||
	sort.Sort(CvePacksList(unsecurePacks))
 | 
			
		||||
	l.setUnsecurePackages(unsecurePacks)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								scan/linux_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								scan/linux_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package scan
 | 
			
		||||
							
								
								
									
										861
									
								
								scan/redhat.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										861
									
								
								scan/redhat.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,861 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/cveapi"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// inherit OsTypeInterface
 | 
			
		||||
type redhat struct {
 | 
			
		||||
	linux
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewRedhat is constructor
 | 
			
		||||
func newRedhat(c config.ServerInfo) *redhat {
 | 
			
		||||
	r := &redhat{}
 | 
			
		||||
	r.log = util.NewCustomLogger(c)
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// https://github.com/serverspec/specinfra/blob/master/lib/specinfra/helper/detect_os/redhat.rb
 | 
			
		||||
func detectRedhat(c config.ServerInfo) (itsMe bool, red osTypeInterface) {
 | 
			
		||||
 | 
			
		||||
	red = newRedhat(c)
 | 
			
		||||
 | 
			
		||||
	// set sudo option flag
 | 
			
		||||
	c.SudoOpt = config.SudoOption{ExecBySudoSh: true}
 | 
			
		||||
	red.setServerInfo(c)
 | 
			
		||||
 | 
			
		||||
	if r := sshExec(c, "ls /etc/fedora-release", noSudo); r.isSuccess() {
 | 
			
		||||
		red.setDistributionInfo("fedora", "unknown")
 | 
			
		||||
		Log.Warn("Fedora not tested yet. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
		return true, red
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := sshExec(c, "ls /etc/redhat-release", noSudo); r.isSuccess() {
 | 
			
		||||
		// https://www.rackaid.com/blog/how-to-determine-centos-or-red-hat-version/
 | 
			
		||||
		// e.g.
 | 
			
		||||
		// $ cat /etc/redhat-release
 | 
			
		||||
		// CentOS release 6.5 (Final)
 | 
			
		||||
		if r := sshExec(c, "cat /etc/redhat-release", noSudo); r.isSuccess() {
 | 
			
		||||
			re, _ := regexp.Compile(`(.*) release (\d[\d.]*)`)
 | 
			
		||||
			result := re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
 | 
			
		||||
			if len(result) != 3 {
 | 
			
		||||
				Log.Warn(
 | 
			
		||||
					"Failed to parse RedHat/CentOS version. stdout: %s, Host: %s:%s",
 | 
			
		||||
					r.Stdout, c.Host, c.Port)
 | 
			
		||||
				return true, red
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			release := result[2]
 | 
			
		||||
			switch strings.ToLower(result[1]) {
 | 
			
		||||
			case "centos", "centos linux":
 | 
			
		||||
				red.setDistributionInfo("centos", release)
 | 
			
		||||
			default:
 | 
			
		||||
				red.setDistributionInfo("rhel", release)
 | 
			
		||||
			}
 | 
			
		||||
			return true, red
 | 
			
		||||
		}
 | 
			
		||||
		return true, red
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := sshExec(c, "ls /etc/system-release", noSudo); r.isSuccess() {
 | 
			
		||||
		family := "amazon"
 | 
			
		||||
		release := "unknown"
 | 
			
		||||
		if r := sshExec(c, "cat /etc/system-release", noSudo); r.isSuccess() {
 | 
			
		||||
			fields := strings.Fields(r.Stdout)
 | 
			
		||||
			if len(fields) == 5 {
 | 
			
		||||
				release = fields[4]
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		red.setDistributionInfo(family, release)
 | 
			
		||||
		return true, red
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Debugf("Not RedHat like Linux. Host: %s:%s", c.Host, c.Port)
 | 
			
		||||
	return false, red
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CentOS 5 ... yum-plugin-security, yum-changelog
 | 
			
		||||
// CentOS 6 ... yum-plugin-security, yum-plugin-changelog
 | 
			
		||||
// CentOS 7 ... yum-plugin-security, yum-plugin-changelog
 | 
			
		||||
// RHEL, Amazon ... no additinal packages needed
 | 
			
		||||
func (o *redhat) install() error {
 | 
			
		||||
 | 
			
		||||
	switch o.Family {
 | 
			
		||||
	case "rhel", "amazon":
 | 
			
		||||
		o.log.Infof("Nothing to do")
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := o.installYumPluginSecurity(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return o.installYumChangelog()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) installYumPluginSecurity() error {
 | 
			
		||||
 | 
			
		||||
	if r := o.ssh("rpm -q yum-plugin-security", noSudo); r.isSuccess() {
 | 
			
		||||
		o.log.Infof("Ignored: yum-plugin-security already installed")
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := util.PrependProxyEnv("yum install -y yum-plugin-security")
 | 
			
		||||
	if r := o.ssh(cmd, sudo); !r.isSuccess() {
 | 
			
		||||
		return fmt.Errorf(
 | 
			
		||||
			"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
			cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) installYumChangelog() error {
 | 
			
		||||
	o.log.Info("Installing yum-plugin-security...")
 | 
			
		||||
 | 
			
		||||
	if o.Family == "centos" {
 | 
			
		||||
		var majorVersion int
 | 
			
		||||
		if 0 < len(o.Release) {
 | 
			
		||||
			majorVersion, _ = strconv.Atoi(strings.Split(o.Release, ".")[0])
 | 
			
		||||
		} else {
 | 
			
		||||
			return fmt.Errorf(
 | 
			
		||||
				"Not implemented yet. family: %s, release: %s",
 | 
			
		||||
				o.Family, o.Release)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var packName = ""
 | 
			
		||||
		if majorVersion < 6 {
 | 
			
		||||
			packName = "yum-changelog"
 | 
			
		||||
		} else {
 | 
			
		||||
			packName = "yum-plugin-changelog"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cmd := "rpm -q " + packName
 | 
			
		||||
		if r := o.ssh(cmd, noSudo); r.isSuccess() {
 | 
			
		||||
			o.log.Infof("Ignored: %s already installed.", packName)
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cmd = util.PrependProxyEnv("yum install -y " + packName)
 | 
			
		||||
		if r := o.ssh(cmd, sudo); !r.isSuccess() {
 | 
			
		||||
			return fmt.Errorf(
 | 
			
		||||
				"Failed to install %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
				packName, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
		}
 | 
			
		||||
		o.log.Infof("Installed: %s.", packName)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) checkRequiredPackagesInstalled() error {
 | 
			
		||||
	if config.Conf.UseYumPluginSecurity {
 | 
			
		||||
		// check if yum-plugin-security is installed.
 | 
			
		||||
		// Amazon Linux, REHL can execute 'yum updateinfo --security updates' without yum-plugin-security
 | 
			
		||||
		cmd := "rpm -q yum-plugin-security"
 | 
			
		||||
		if o.Family == "centos" {
 | 
			
		||||
			if r := o.ssh(cmd, noSudo); !r.isSuccess() {
 | 
			
		||||
				msg := "yum-plugin-security is not installed"
 | 
			
		||||
				o.log.Errorf(msg)
 | 
			
		||||
				return fmt.Errorf(msg)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.Family == "centos" {
 | 
			
		||||
		var majorVersion int
 | 
			
		||||
		if 0 < len(o.Release) {
 | 
			
		||||
			majorVersion, _ = strconv.Atoi(strings.Split(o.Release, ".")[0])
 | 
			
		||||
		} else {
 | 
			
		||||
			msg := fmt.Sprintf("Not implemented yet. family: %s, release: %s", o.Family, o.Release)
 | 
			
		||||
			o.log.Errorf(msg)
 | 
			
		||||
			return fmt.Errorf(msg)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var packName = ""
 | 
			
		||||
		if majorVersion < 6 {
 | 
			
		||||
			packName = "yum-changelog"
 | 
			
		||||
		} else {
 | 
			
		||||
			packName = "yum-plugin-changelog"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cmd := "rpm -q " + packName
 | 
			
		||||
		if r := o.ssh(cmd, noSudo); !r.isSuccess() {
 | 
			
		||||
			msg := fmt.Sprintf("%s is not installed", packName)
 | 
			
		||||
			o.log.Errorf(msg)
 | 
			
		||||
			return fmt.Errorf(msg)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) scanPackages() error {
 | 
			
		||||
	var err error
 | 
			
		||||
	var packs []models.PackageInfo
 | 
			
		||||
	if packs, err = o.scanInstalledPackages(); err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan installed packages")
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.setPackages(packs)
 | 
			
		||||
 | 
			
		||||
	var unsecurePacks []CvePacksInfo
 | 
			
		||||
	if unsecurePacks, err = o.scanUnsecurePackages(); err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan valnerable packages")
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.setUnsecurePackages(unsecurePacks)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) scanInstalledPackages() (installedPackages models.PackageInfoList, err error) {
 | 
			
		||||
	cmd := "rpm -qa --queryformat '%{NAME}\t%{VERSION}\t%{RELEASE}\n'"
 | 
			
		||||
	r := o.ssh(cmd, noSudo)
 | 
			
		||||
	if r.isSuccess() {
 | 
			
		||||
		//  e.g.
 | 
			
		||||
		// openssl	1.0.1e	30.el6.11
 | 
			
		||||
		lines := strings.Split(r.Stdout, "\n")
 | 
			
		||||
		for _, line := range lines {
 | 
			
		||||
			if trimed := strings.TrimSpace(line); len(trimed) != 0 {
 | 
			
		||||
				var packinfo models.PackageInfo
 | 
			
		||||
				if packinfo, err = o.parseScanedPackagesLine(line); err != nil {
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				installedPackages = append(installedPackages, packinfo)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return installedPackages, fmt.Errorf(
 | 
			
		||||
		"Scan packages failed. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
		r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) parseScanedPackagesLine(line string) (pack models.PackageInfo, err error) {
 | 
			
		||||
	re, _ := regexp.Compile(`^([^\t']+)\t([^\t]+)\t(.+)$`)
 | 
			
		||||
	result := re.FindStringSubmatch(line)
 | 
			
		||||
	if len(result) == 4 {
 | 
			
		||||
		pack.Name = result[1]
 | 
			
		||||
		pack.Version = result[2]
 | 
			
		||||
		pack.Release = strings.TrimSpace(result[3])
 | 
			
		||||
	} else {
 | 
			
		||||
		err = fmt.Errorf("redhat: Failed to parse package line: %s", line)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) scanUnsecurePackages() ([]CvePacksInfo, error) {
 | 
			
		||||
	if o.Family != "centos" || config.Conf.UseYumPluginSecurity {
 | 
			
		||||
		// Amazon, RHEL has yum updateinfo as default
 | 
			
		||||
		// yum updateinfo can collenct vendor advisory information.
 | 
			
		||||
		return o.scanUnsecurePackagesUsingYumPluginSecurity()
 | 
			
		||||
	}
 | 
			
		||||
	// CentOS does not have security channel...
 | 
			
		||||
	// So, yum check-update then parse chnagelog.
 | 
			
		||||
	return o.scanUnsecurePackagesUsingYumCheckUpdate()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//TODO return whether already expired.
 | 
			
		||||
func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (CvePacksList, error) {
 | 
			
		||||
 | 
			
		||||
	cmd := "yum check-update"
 | 
			
		||||
	r := o.ssh(util.PrependProxyEnv(cmd), sudo)
 | 
			
		||||
	if !r.isSuccess(0, 100) {
 | 
			
		||||
		//returns an exit code of 100 if there are available updates.
 | 
			
		||||
		return nil, fmt.Errorf(
 | 
			
		||||
			"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
			cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// get Updateble package name, installed, candidate version.
 | 
			
		||||
	packInfoList, err := o.parseYumCheckUpdateLines(r.Stdout)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to parse %s. err: %s", cmd, err)
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Debugf("%s", pp.Sprintf("%s", packInfoList))
 | 
			
		||||
 | 
			
		||||
	// Collect CVE-IDs in changelog
 | 
			
		||||
	type PackInfoCveIDs struct {
 | 
			
		||||
		PackInfo models.PackageInfo
 | 
			
		||||
		CveIDs   []string
 | 
			
		||||
	}
 | 
			
		||||
	var results []PackInfoCveIDs
 | 
			
		||||
	for i, packInfo := range packInfoList {
 | 
			
		||||
		changelog, err := o.getChangelog(packInfo.Name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			o.log.Errorf("Failed to collect CVE. err: %s", err)
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Collect unique set of CVE-ID in each changelog
 | 
			
		||||
		uniqueCveIDMap := make(map[string]bool)
 | 
			
		||||
		lines := strings.Split(changelog, "\n")
 | 
			
		||||
		for _, line := range lines {
 | 
			
		||||
			cveIDs := o.parseYumUpdateinfoLineToGetCveIDs(line)
 | 
			
		||||
			for _, c := range cveIDs {
 | 
			
		||||
				uniqueCveIDMap[c] = true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// keys
 | 
			
		||||
		var cveIDs []string
 | 
			
		||||
		for k := range uniqueCveIDMap {
 | 
			
		||||
			cveIDs = append(cveIDs, k)
 | 
			
		||||
		}
 | 
			
		||||
		p := PackInfoCveIDs{
 | 
			
		||||
			PackInfo: packInfo,
 | 
			
		||||
			CveIDs:   cveIDs,
 | 
			
		||||
		}
 | 
			
		||||
		results = append(results, p)
 | 
			
		||||
 | 
			
		||||
		o.log.Infof("(%d/%d) Scanned %s-%s-%s -> %s-%s : %s",
 | 
			
		||||
			i+1,
 | 
			
		||||
			len(packInfoList),
 | 
			
		||||
			p.PackInfo.Name,
 | 
			
		||||
			p.PackInfo.Version,
 | 
			
		||||
			p.PackInfo.Release,
 | 
			
		||||
			p.PackInfo.NewVersion,
 | 
			
		||||
			p.PackInfo.NewRelease,
 | 
			
		||||
			p.CveIDs)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// transform datastructure
 | 
			
		||||
	// - From
 | 
			
		||||
	// [
 | 
			
		||||
	//   {
 | 
			
		||||
	//     PackInfo:    models.PackageInfo,
 | 
			
		||||
	//     CveIDs:      []string,
 | 
			
		||||
	//   },
 | 
			
		||||
	// ]
 | 
			
		||||
	// - To
 | 
			
		||||
	// map {
 | 
			
		||||
	//   CveID: []models.PackageInfo
 | 
			
		||||
	// }
 | 
			
		||||
	cveIDPackInfoMap := make(map[string][]models.PackageInfo)
 | 
			
		||||
	for _, res := range results {
 | 
			
		||||
		for _, cveID := range res.CveIDs {
 | 
			
		||||
			//  packInfo, found := o.Packages.FindByName(res.Packname)
 | 
			
		||||
			//  if !found {
 | 
			
		||||
			//      return CvePacksList{}, fmt.Errorf(
 | 
			
		||||
			//          "Faild to transform data structure: %v", res.Packname)
 | 
			
		||||
			//  }
 | 
			
		||||
			cveIDPackInfoMap[cveID] = append(cveIDPackInfoMap[cveID], res.PackInfo)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var uniqueCveIDs []string
 | 
			
		||||
	for cveID := range cveIDPackInfoMap {
 | 
			
		||||
		uniqueCveIDs = append(uniqueCveIDs, cveID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// cveIDs => []cve.CveInfo
 | 
			
		||||
	o.log.Info("Fetching CVE details...")
 | 
			
		||||
	cveDetails, err := cveapi.CveClient.FetchCveDetails(uniqueCveIDs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Info("Done")
 | 
			
		||||
 | 
			
		||||
	cvePacksList := []CvePacksInfo{}
 | 
			
		||||
	for _, detail := range cveDetails {
 | 
			
		||||
		// Amazon, RHEL do not use this method, so VendorAdvisory do not set.
 | 
			
		||||
		cvePacksList = append(cvePacksList, CvePacksInfo{
 | 
			
		||||
			CveID:     detail.CveID,
 | 
			
		||||
			CveDetail: detail,
 | 
			
		||||
			Packs:     cveIDPackInfoMap[detail.CveID],
 | 
			
		||||
			//  CvssScore: cinfo.CvssScore(conf.Lang),
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	sort.Sort(CvePacksList(cvePacksList))
 | 
			
		||||
	return cvePacksList, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseYumCheckUpdateLines parse yum check-update to get package name, candidate version
 | 
			
		||||
func (o *redhat) parseYumCheckUpdateLines(stdout string) (results models.PackageInfoList, err error) {
 | 
			
		||||
	needToParse := false
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
	for _, line := range lines {
 | 
			
		||||
		// update information of packages begin after blank line.
 | 
			
		||||
		if trimed := strings.TrimSpace(line); len(trimed) == 0 {
 | 
			
		||||
			needToParse = true
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if needToParse {
 | 
			
		||||
			candidate, err := o.parseYumCheckUpdateLine(line)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return models.PackageInfoList{}, err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			installed, found := o.Packages.FindByName(candidate.Name)
 | 
			
		||||
			if !found {
 | 
			
		||||
				return models.PackageInfoList{}, fmt.Errorf(
 | 
			
		||||
					"Failed to parse yum check update line: %s-%s-%s",
 | 
			
		||||
					candidate.Name, candidate.Version, candidate.Release)
 | 
			
		||||
			}
 | 
			
		||||
			installed.NewVersion = candidate.NewVersion
 | 
			
		||||
			installed.NewRelease = candidate.NewRelease
 | 
			
		||||
			results = append(results, installed)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) parseYumCheckUpdateLine(line string) (models.PackageInfo, error) {
 | 
			
		||||
	fields := strings.Fields(line)
 | 
			
		||||
	if len(fields) != 3 {
 | 
			
		||||
		return models.PackageInfo{}, fmt.Errorf("Unknown format: %s", line)
 | 
			
		||||
	}
 | 
			
		||||
	splitted := strings.Split(fields[0], ".")
 | 
			
		||||
	packName := ""
 | 
			
		||||
	if len(splitted) == 1 {
 | 
			
		||||
		packName = fields[0]
 | 
			
		||||
	} else {
 | 
			
		||||
		packName = strings.Join(strings.Split(fields[0], ".")[0:(len(splitted)-1)], ".")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fields = strings.Split(fields[1], "-")
 | 
			
		||||
	if len(fields) != 2 {
 | 
			
		||||
		return models.PackageInfo{}, fmt.Errorf("Unknown format: %s", line)
 | 
			
		||||
	}
 | 
			
		||||
	version := fields[0]
 | 
			
		||||
	release := fields[1]
 | 
			
		||||
	return models.PackageInfo{
 | 
			
		||||
		Name:       packName,
 | 
			
		||||
		NewVersion: version,
 | 
			
		||||
		NewRelease: release,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) getChangelog(packageNames string) (stdout string, err error) {
 | 
			
		||||
	command := "echo N | "
 | 
			
		||||
	if 0 < len(config.Conf.HTTPProxy) {
 | 
			
		||||
		command += util.ProxyEnv()
 | 
			
		||||
	}
 | 
			
		||||
	command += fmt.Sprintf(" yum update --changelog %s | grep CVE", packageNames)
 | 
			
		||||
 | 
			
		||||
	r := o.ssh(command, sudo)
 | 
			
		||||
	if !r.isSuccess(0, 1) {
 | 
			
		||||
		return "", fmt.Errorf(
 | 
			
		||||
			"Failed to get changelog. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
			r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
	}
 | 
			
		||||
	return r.Stdout, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type distroAdvisoryCveIDs struct {
 | 
			
		||||
	DistroAdvisory models.DistroAdvisory
 | 
			
		||||
	CveIDs         []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Scaning unsecure packages using yum-plugin-security.
 | 
			
		||||
//TODO return whether already expired.
 | 
			
		||||
func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (CvePacksList, error) {
 | 
			
		||||
	if o.Family == "centos" {
 | 
			
		||||
		// CentOS has no security channel.
 | 
			
		||||
		// So use yum check-update && parse changelog
 | 
			
		||||
		return CvePacksList{}, fmt.Errorf(
 | 
			
		||||
			"yum updateinfo is not suppported on CentOS")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd := "yum repolist"
 | 
			
		||||
	r := o.ssh(util.PrependProxyEnv(cmd), sudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return nil, fmt.Errorf(
 | 
			
		||||
			"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
			cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// get advisoryID(RHSA, ALAS) - package name,version
 | 
			
		||||
	cmd = "yum updateinfo list available --security"
 | 
			
		||||
	r = o.ssh(util.PrependProxyEnv(cmd), sudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return nil, fmt.Errorf(
 | 
			
		||||
			"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
			cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
	}
 | 
			
		||||
	advIDPackNamesList, err := o.parseYumUpdateinfoListAvailable(r.Stdout)
 | 
			
		||||
 | 
			
		||||
	// get package name, version, rel to be upgrade.
 | 
			
		||||
	cmd = "yum check-update --security"
 | 
			
		||||
	r = o.ssh(util.PrependProxyEnv(cmd), sudo)
 | 
			
		||||
	if !r.isSuccess(0, 100) {
 | 
			
		||||
		//returns an exit code of 100 if there are available updates.
 | 
			
		||||
		return nil, fmt.Errorf(
 | 
			
		||||
			"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
			cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
	}
 | 
			
		||||
	vulnerablePackInfoList, err := o.parseYumCheckUpdateLines(r.Stdout)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to parse %s. err: %s", cmd, err)
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Debugf("%s", pp.Sprintf("%s", vulnerablePackInfoList))
 | 
			
		||||
	for i, packInfo := range vulnerablePackInfoList {
 | 
			
		||||
		installedPack, found := o.Packages.FindByName(packInfo.Name)
 | 
			
		||||
		if !found {
 | 
			
		||||
			return nil, fmt.Errorf(
 | 
			
		||||
				"Parsed package not found. packInfo: %#v", packInfo)
 | 
			
		||||
		}
 | 
			
		||||
		vulnerablePackInfoList[i].Version = installedPack.Version
 | 
			
		||||
		vulnerablePackInfoList[i].Release = installedPack.Release
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dict := map[string][]models.PackageInfo{}
 | 
			
		||||
	for _, advIDPackNames := range advIDPackNamesList {
 | 
			
		||||
		packInfoList := models.PackageInfoList{}
 | 
			
		||||
		for _, packName := range advIDPackNames.PackNames {
 | 
			
		||||
			packInfo, found := vulnerablePackInfoList.FindByName(packName)
 | 
			
		||||
			if !found {
 | 
			
		||||
				return nil, fmt.Errorf(
 | 
			
		||||
					"PackInfo not found. packInfo: %#v", packName)
 | 
			
		||||
			}
 | 
			
		||||
			packInfoList = append(packInfoList, packInfo)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		dict[advIDPackNames.AdvisoryID] = packInfoList
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// get advisoryID(RHSA, ALAS) - CVE IDs
 | 
			
		||||
	cmd = "yum updateinfo --security update"
 | 
			
		||||
	r = o.ssh(util.PrependProxyEnv(cmd), noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return nil, fmt.Errorf(
 | 
			
		||||
			"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
			
		||||
			cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
	}
 | 
			
		||||
	advisoryCveIDsList, err := o.parseYumUpdateinfo(r.Stdout)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return CvePacksList{}, err
 | 
			
		||||
	}
 | 
			
		||||
	//  pp.Println(advisoryCveIDsList)
 | 
			
		||||
 | 
			
		||||
	// All information collected.
 | 
			
		||||
	// Convert to CvePacksList.
 | 
			
		||||
	o.log.Info("Fetching CVE details...")
 | 
			
		||||
	result := CvePacksList{}
 | 
			
		||||
	for _, advIDCveIDs := range advisoryCveIDsList {
 | 
			
		||||
		cveDetails, err :=
 | 
			
		||||
			cveapi.CveClient.FetchCveDetails(advIDCveIDs.CveIDs)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, cveDetail := range cveDetails {
 | 
			
		||||
			found := false
 | 
			
		||||
			for i, p := range result {
 | 
			
		||||
				if cveDetail.CveID == p.CveID {
 | 
			
		||||
					advAppended := append(p.DistroAdvisories, advIDCveIDs.DistroAdvisory)
 | 
			
		||||
					result[i].DistroAdvisories = advAppended
 | 
			
		||||
 | 
			
		||||
					packs := dict[advIDCveIDs.DistroAdvisory.AdvisoryID]
 | 
			
		||||
					result[i].Packs = append(result[i].Packs, packs...)
 | 
			
		||||
					found = true
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if !found {
 | 
			
		||||
				cpinfo := CvePacksInfo{
 | 
			
		||||
					CveID:            cveDetail.CveID,
 | 
			
		||||
					CveDetail:        cveDetail,
 | 
			
		||||
					DistroAdvisories: []models.DistroAdvisory{advIDCveIDs.DistroAdvisory},
 | 
			
		||||
					Packs:            dict[advIDCveIDs.DistroAdvisory.AdvisoryID],
 | 
			
		||||
				}
 | 
			
		||||
				result = append(result, cpinfo)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Info("Done")
 | 
			
		||||
	return result, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) parseYumUpdateinfo(stdout string) (result []distroAdvisoryCveIDs, err error) {
 | 
			
		||||
	sectionState := Outside
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
	lines = append(lines, "=============")
 | 
			
		||||
 | 
			
		||||
	// Amazon Linux AMI Security Information
 | 
			
		||||
	advisory := models.DistroAdvisory{}
 | 
			
		||||
 | 
			
		||||
	cveIDsSetInThisSection := make(map[string]bool)
 | 
			
		||||
 | 
			
		||||
	// use this flag to Collect CVE IDs in CVEs field.
 | 
			
		||||
	var inDesctiption = false
 | 
			
		||||
 | 
			
		||||
	for _, line := range lines {
 | 
			
		||||
		line = strings.TrimSpace(line)
 | 
			
		||||
 | 
			
		||||
		// find the new section pattern
 | 
			
		||||
		if match, _ := o.isHorizontalRule(line); match {
 | 
			
		||||
 | 
			
		||||
			// set previous section's result to return-variable
 | 
			
		||||
			if sectionState == Content {
 | 
			
		||||
 | 
			
		||||
				foundCveIDs := []string{}
 | 
			
		||||
				for cveID := range cveIDsSetInThisSection {
 | 
			
		||||
					foundCveIDs = append(foundCveIDs, cveID)
 | 
			
		||||
				}
 | 
			
		||||
				sort.Strings(foundCveIDs)
 | 
			
		||||
				result = append(result, distroAdvisoryCveIDs{
 | 
			
		||||
					DistroAdvisory: advisory,
 | 
			
		||||
					CveIDs:         foundCveIDs,
 | 
			
		||||
				})
 | 
			
		||||
 | 
			
		||||
				// reset for next section.
 | 
			
		||||
				cveIDsSetInThisSection = make(map[string]bool)
 | 
			
		||||
				inDesctiption = false
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Go to next section
 | 
			
		||||
			sectionState = o.changeSectionState(sectionState)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch sectionState {
 | 
			
		||||
		case Header:
 | 
			
		||||
			switch o.Family {
 | 
			
		||||
			case "centos":
 | 
			
		||||
				// CentOS has no security channel.
 | 
			
		||||
				// So use yum check-update && parse changelog
 | 
			
		||||
				return result, fmt.Errorf(
 | 
			
		||||
					"yum updateinfo is not suppported on  CentOS")
 | 
			
		||||
			case "rhel", "amazon":
 | 
			
		||||
				// nop
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		case Content:
 | 
			
		||||
			if found := o.isDescriptionLine(line); found {
 | 
			
		||||
				inDesctiption = true
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// severity
 | 
			
		||||
			severity, found := o.parseYumUpdateinfoToGetSeverity(line)
 | 
			
		||||
			if found {
 | 
			
		||||
				advisory.Severity = severity
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// No need to parse in description except severity
 | 
			
		||||
			if inDesctiption {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			cveIDs := o.parseYumUpdateinfoLineToGetCveIDs(line)
 | 
			
		||||
			for _, cveID := range cveIDs {
 | 
			
		||||
				cveIDsSetInThisSection[cveID] = true
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			advisoryID, found := o.parseYumUpdateinfoToGetAdvisoryID(line)
 | 
			
		||||
			if found {
 | 
			
		||||
				advisory.AdvisoryID = advisoryID
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			issued, found := o.parseYumUpdateinfoLineToGetIssued(line)
 | 
			
		||||
			if found {
 | 
			
		||||
				advisory.Issued = issued
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			updated, found := o.parseYumUpdateinfoLineToGetUpdated(line)
 | 
			
		||||
			if found {
 | 
			
		||||
				advisory.Updated = updated
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// state
 | 
			
		||||
const (
 | 
			
		||||
	Outside = iota
 | 
			
		||||
	Header  = iota
 | 
			
		||||
	Content = iota
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (o *redhat) changeSectionState(state int) (newState int) {
 | 
			
		||||
	switch state {
 | 
			
		||||
	case Outside, Content:
 | 
			
		||||
		newState = Header
 | 
			
		||||
	case Header:
 | 
			
		||||
		newState = Content
 | 
			
		||||
	}
 | 
			
		||||
	return newState
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) isHorizontalRule(line string) (bool, error) {
 | 
			
		||||
	return regexp.MatchString("^=+$", line)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// see test case
 | 
			
		||||
func (o *redhat) parseYumUpdateinfoHeaderCentOS(line string) (packs []models.PackageInfo, err error) {
 | 
			
		||||
	pkgs := strings.Split(strings.TrimSpace(line), ",")
 | 
			
		||||
	for _, pkg := range pkgs {
 | 
			
		||||
		packs = append(packs, models.PackageInfo{})
 | 
			
		||||
		s := strings.Split(pkg, "-")
 | 
			
		||||
		if len(s) == 3 {
 | 
			
		||||
			packs[len(packs)-1].Name = s[0]
 | 
			
		||||
			packs[len(packs)-1].Version = s[1]
 | 
			
		||||
			packs[len(packs)-1].Release = s[2]
 | 
			
		||||
		} else {
 | 
			
		||||
			return packs, fmt.Errorf("CentOS: Unknown Header format: %s", line)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) parseYumUpdateinfoHeaderAmazon(line string) (a models.DistroAdvisory, names []string, err error) {
 | 
			
		||||
	re, _ := regexp.Compile(`(ALAS-.+): (.+) priority package update for (.+)$`)
 | 
			
		||||
	result := re.FindStringSubmatch(line)
 | 
			
		||||
	if len(result) == 4 {
 | 
			
		||||
		a.AdvisoryID = result[1]
 | 
			
		||||
		a.Severity = result[2]
 | 
			
		||||
		spaceSeparatedPacknames := result[3]
 | 
			
		||||
		names = strings.Fields(spaceSeparatedPacknames)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	err = fmt.Errorf("Amazon Linux: Unknown Header Format. %s", line)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) parseYumUpdateinfoLineToGetCveIDs(line string) []string {
 | 
			
		||||
	re, _ := regexp.Compile(`(CVE-\d{4}-\d{4})`)
 | 
			
		||||
	return re.FindAllString(line, -1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) parseYumUpdateinfoToGetAdvisoryID(line string) (advisoryID string, found bool) {
 | 
			
		||||
	re, _ := regexp.Compile(`^ *Update ID : (.*)$`)
 | 
			
		||||
	result := re.FindStringSubmatch(line)
 | 
			
		||||
	if len(result) != 2 {
 | 
			
		||||
		return "", false
 | 
			
		||||
	}
 | 
			
		||||
	return strings.TrimSpace(result[1]), true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) parseYumUpdateinfoLineToGetIssued(line string) (date time.Time, found bool) {
 | 
			
		||||
	return o.parseYumUpdateinfoLineToGetDate(line, `^\s*Issued : (\d{4}-\d{2}-\d{2})`)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) parseYumUpdateinfoLineToGetUpdated(line string) (date time.Time, found bool) {
 | 
			
		||||
	return o.parseYumUpdateinfoLineToGetDate(line, `^\s*Updated : (\d{4}-\d{2}-\d{2})`)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) parseYumUpdateinfoLineToGetDate(line, regexpFormat string) (date time.Time, found bool) {
 | 
			
		||||
	re, _ := regexp.Compile(regexpFormat)
 | 
			
		||||
	result := re.FindStringSubmatch(line)
 | 
			
		||||
	if len(result) != 2 {
 | 
			
		||||
		return date, false
 | 
			
		||||
	}
 | 
			
		||||
	t, err := time.Parse("2006-01-02", result[1])
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return date, false
 | 
			
		||||
	}
 | 
			
		||||
	return t, true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) isDescriptionLine(line string) bool {
 | 
			
		||||
	re, _ := regexp.Compile(`^\s*Description : `)
 | 
			
		||||
	return re.MatchString(line)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) parseYumUpdateinfoToGetSeverity(line string) (severity string, found bool) {
 | 
			
		||||
	re, _ := regexp.Compile(`^ *Severity : (.*)$`)
 | 
			
		||||
	result := re.FindStringSubmatch(line)
 | 
			
		||||
	if len(result) != 2 {
 | 
			
		||||
		return "", false
 | 
			
		||||
	}
 | 
			
		||||
	return strings.TrimSpace(result[1]), true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type advisoryIDPacks struct {
 | 
			
		||||
	AdvisoryID string
 | 
			
		||||
	PackNames  []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type advisoryIDPacksList []advisoryIDPacks
 | 
			
		||||
 | 
			
		||||
func (list advisoryIDPacksList) find(advisoryID string) (advisoryIDPacks, bool) {
 | 
			
		||||
	for _, a := range list {
 | 
			
		||||
		if a.AdvisoryID == advisoryID {
 | 
			
		||||
			return a, true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return advisoryIDPacks{}, false
 | 
			
		||||
}
 | 
			
		||||
func (o *redhat) extractPackNameVerRel(nameVerRel string) (name, ver, rel string) {
 | 
			
		||||
	fields := strings.Split(nameVerRel, ".")
 | 
			
		||||
	archTrimed := strings.Join(fields[0:len(fields)-1], ".")
 | 
			
		||||
 | 
			
		||||
	fields = strings.Split(archTrimed, "-")
 | 
			
		||||
	rel = fields[len(fields)-1]
 | 
			
		||||
	ver = fields[len(fields)-2]
 | 
			
		||||
	name = strings.Join(fields[0:(len(fields)-2)], "-")
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseYumUpdateinfoListAvailable collect AdvisorID(RHSA, ALAS), packages
 | 
			
		||||
func (o *redhat) parseYumUpdateinfoListAvailable(stdout string) (advisoryIDPacksList, error) {
 | 
			
		||||
 | 
			
		||||
	result := []advisoryIDPacks{}
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
	for _, line := range lines {
 | 
			
		||||
 | 
			
		||||
		if !(strings.HasPrefix(line, "RHSA") ||
 | 
			
		||||
			strings.HasPrefix(line, "ALAS")) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fields := strings.Fields(line)
 | 
			
		||||
		if len(fields) != 3 {
 | 
			
		||||
			return []advisoryIDPacks{}, fmt.Errorf(
 | 
			
		||||
				"Unknown format. line: %s", line)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// extract fields
 | 
			
		||||
		advisoryID := fields[0]
 | 
			
		||||
		packVersion := fields[2]
 | 
			
		||||
		packName, _, _ := o.extractPackNameVerRel(packVersion)
 | 
			
		||||
 | 
			
		||||
		found := false
 | 
			
		||||
		for i, s := range result {
 | 
			
		||||
			if s.AdvisoryID == advisoryID {
 | 
			
		||||
				names := s.PackNames
 | 
			
		||||
				names = append(names, packName)
 | 
			
		||||
				result[i].PackNames = names
 | 
			
		||||
				found = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !found {
 | 
			
		||||
			result = append(result, advisoryIDPacks{
 | 
			
		||||
				AdvisoryID: advisoryID,
 | 
			
		||||
				PackNames:  []string{packName},
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return result, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										843
									
								
								scan/redhat_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										843
									
								
								scan/redhat_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,843 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//  func unixtimeNoerr(s string) time.Time {
 | 
			
		||||
//      t, _ := unixtime(s)
 | 
			
		||||
//      return t
 | 
			
		||||
//  }
 | 
			
		||||
 | 
			
		||||
func TestParseScanedPackagesLineRedhat(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	var packagetests = []struct {
 | 
			
		||||
		in   string
 | 
			
		||||
		pack models.PackageInfo
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			"openssl	1.0.1e	30.el6.11",
 | 
			
		||||
			models.PackageInfo{
 | 
			
		||||
				Name:    "openssl",
 | 
			
		||||
				Version: "1.0.1e",
 | 
			
		||||
				Release: "30.el6.11",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range packagetests {
 | 
			
		||||
		p, _ := r.parseScanedPackagesLine(tt.in)
 | 
			
		||||
		if p.Name != tt.pack.Name {
 | 
			
		||||
			t.Errorf("name: expected %s, actual %s", tt.pack.Name, p.Name)
 | 
			
		||||
		}
 | 
			
		||||
		if p.Version != tt.pack.Version {
 | 
			
		||||
			t.Errorf("version: expected %s, actual %s", tt.pack.Version, p.Version)
 | 
			
		||||
		}
 | 
			
		||||
		if p.Release != tt.pack.Release {
 | 
			
		||||
			t.Errorf("release: expected %s, actual %s", tt.pack.Release, p.Release)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestChangeSectionState(t *testing.T) {
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		oldState int
 | 
			
		||||
		newState int
 | 
			
		||||
	}{
 | 
			
		||||
		{Outside, Header},
 | 
			
		||||
		{Header, Content},
 | 
			
		||||
		{Content, Header},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		if n := r.changeSectionState(tt.oldState); n != tt.newState {
 | 
			
		||||
			t.Errorf("expected %d, actual %d", tt.newState, n)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseYumUpdateinfoHeader(t *testing.T) {
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
		out []models.PackageInfo
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			"     nodejs-0.10.36-3.el6,libuv-0.10.34-1.el6,v8-3.14.5.10-17.el6    ",
 | 
			
		||||
			[]models.PackageInfo{
 | 
			
		||||
				{
 | 
			
		||||
					Name:    "nodejs",
 | 
			
		||||
					Version: "0.10.36",
 | 
			
		||||
					Release: "3.el6",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:    "libuv",
 | 
			
		||||
					Version: "0.10.34",
 | 
			
		||||
					Release: "1.el6",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:    "v8",
 | 
			
		||||
					Version: "3.14.5.10",
 | 
			
		||||
					Release: "17.el6",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		if a, err := r.parseYumUpdateinfoHeaderCentOS(tt.in); err != nil {
 | 
			
		||||
			t.Errorf("err: %s", err)
 | 
			
		||||
		} else {
 | 
			
		||||
			if !reflect.DeepEqual(a, tt.out) {
 | 
			
		||||
				e := pp.Sprintf("%#v", tt.out)
 | 
			
		||||
				a := pp.Sprintf("%#v", a)
 | 
			
		||||
				t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseYumUpdateinfoLineToGetCveIDs(t *testing.T) {
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
		out []string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			"Bugs : 1194651 - CVE-2015-0278 libuv:",
 | 
			
		||||
			[]string{"CVE-2015-0278"},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			": 1195457 - nodejs-0.10.35 causes undefined symbolsCVE-2015-0278, CVE-2015-0278, CVE-2015-0277",
 | 
			
		||||
			[]string{
 | 
			
		||||
				"CVE-2015-0278",
 | 
			
		||||
				"CVE-2015-0278",
 | 
			
		||||
				"CVE-2015-0277",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		act := r.parseYumUpdateinfoLineToGetCveIDs(tt.in)
 | 
			
		||||
		for i, s := range act {
 | 
			
		||||
			if s != tt.out[i] {
 | 
			
		||||
				t.Errorf("expected %s, actual %s", tt.out[i], s)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseYumUpdateinfoToGetAdvisoryID(t *testing.T) {
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in    string
 | 
			
		||||
		out   string
 | 
			
		||||
		found bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			"Update ID : RHSA-2015:2315",
 | 
			
		||||
			"RHSA-2015:2315",
 | 
			
		||||
			true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"Update ID : ALAS-2015-620",
 | 
			
		||||
			"ALAS-2015-620",
 | 
			
		||||
			true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"Issued : 2015-11-19 00:00:00",
 | 
			
		||||
			"",
 | 
			
		||||
			false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		advisoryID, found := r.parseYumUpdateinfoToGetAdvisoryID(tt.in)
 | 
			
		||||
		if tt.out != advisoryID {
 | 
			
		||||
			t.Errorf("expected %s, actual %s", tt.out, advisoryID)
 | 
			
		||||
		}
 | 
			
		||||
		if tt.found != found {
 | 
			
		||||
			t.Errorf("expected %t, actual %t", tt.found, found)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseYumUpdateinfoLineToGetIssued(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	date, _ := time.Parse("2006-01-02", "2015-12-15")
 | 
			
		||||
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in    string
 | 
			
		||||
		out   time.Time
 | 
			
		||||
		found bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			"Issued : 2015-12-15 00:00:00",
 | 
			
		||||
			date,
 | 
			
		||||
			true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"        Issued : 2015-12-15 00:00:00     ",
 | 
			
		||||
			date,
 | 
			
		||||
			true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"Type : security",
 | 
			
		||||
			time.Time{},
 | 
			
		||||
			false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		d, found := r.parseYumUpdateinfoLineToGetIssued(tt.in)
 | 
			
		||||
		if tt.found != found {
 | 
			
		||||
			t.Errorf("[%d] line: %s, expected %t, actual %t", i, tt.in, tt.found, found)
 | 
			
		||||
		}
 | 
			
		||||
		if tt.out != d {
 | 
			
		||||
			t.Errorf("[%d] line: %s, expected %v, actual %v", i, tt.in, tt.out, d)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseYumUpdateinfoLineToGetUpdated(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	date, _ := time.Parse("2006-01-02", "2015-12-15")
 | 
			
		||||
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in    string
 | 
			
		||||
		out   time.Time
 | 
			
		||||
		found bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			"Updated : 2015-12-15 00:00:00       Bugs : 1286966 - CVE-2015-8370 grub2: buffer overflow when checking password entered during bootup",
 | 
			
		||||
			date,
 | 
			
		||||
			true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
 | 
			
		||||
			"Updated : 2015-12-15 14:16       CVEs : CVE-2015-7981",
 | 
			
		||||
			date,
 | 
			
		||||
			true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"Type : security",
 | 
			
		||||
			time.Time{},
 | 
			
		||||
			false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		d, found := r.parseYumUpdateinfoLineToGetUpdated(tt.in)
 | 
			
		||||
		if tt.found != found {
 | 
			
		||||
			t.Errorf("[%d] line: %s, expected %t, actual %t", i, tt.in, tt.found, found)
 | 
			
		||||
		}
 | 
			
		||||
		if tt.out != d {
 | 
			
		||||
			t.Errorf("[%d] line: %s, expected %v, actual %v", i, tt.in, tt.out, d)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestIsDescriptionLine(t *testing.T) {
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in    string
 | 
			
		||||
		found bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			"Description : Package updates are available for Amazon Linux AMI that fix the",
 | 
			
		||||
			true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"  Description : Package updates are available for Amazon Linux AMI that fix the",
 | 
			
		||||
			true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"Status : final",
 | 
			
		||||
			false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		found := r.isDescriptionLine(tt.in)
 | 
			
		||||
		if tt.found != found {
 | 
			
		||||
			t.Errorf("[%d] line: %s, expected %t, actual %t", i, tt.in, tt.found, found)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseYumUpdateinfoToGetSeverity(t *testing.T) {
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in    string
 | 
			
		||||
		out   string
 | 
			
		||||
		found bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			"Severity : Moderate",
 | 
			
		||||
			"Moderate",
 | 
			
		||||
			true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"   Severity : medium",
 | 
			
		||||
			"medium",
 | 
			
		||||
			true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"Status : final",
 | 
			
		||||
			"",
 | 
			
		||||
			false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		out, found := r.parseYumUpdateinfoToGetSeverity(tt.in)
 | 
			
		||||
		if tt.found != found {
 | 
			
		||||
			t.Errorf("[%d] line: %s, expected %t, actual %t", i, tt.in, tt.found, found)
 | 
			
		||||
		}
 | 
			
		||||
		if tt.out != out {
 | 
			
		||||
			t.Errorf("[%d] line: %s, expected %v, actual %v", i, tt.in, tt.out, out)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseYumUpdateinfoRHEL(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	stdout := `===============================================================================
 | 
			
		||||
  Important: bind security update
 | 
			
		||||
===============================================================================
 | 
			
		||||
  Update ID : RHSA-2015:1705
 | 
			
		||||
    Release :
 | 
			
		||||
       Type : security
 | 
			
		||||
     Status : final
 | 
			
		||||
     Issued : 2015-09-03 00:00:00
 | 
			
		||||
       Bugs : 1259087 - CVE-2015-5722 bind: malformed DNSSEC key failed assertion denial of service
 | 
			
		||||
       CVEs : CVE-2015-5722
 | 
			
		||||
Description : The Berkeley Internet Name Domain (BIND) is an implementation of
 | 
			
		||||
            : the Domain Name System (DNS) protocols. BIND
 | 
			
		||||
            : includes a DNS server (named); a resolver library
 | 
			
		||||
            : (routines for applications to use when interfacing
 | 
			
		||||
            : with DNS); and tools for verifying that the DNS
 | 
			
		||||
            : server is operating correctly.
 | 
			
		||||
            :
 | 
			
		||||
   Severity : Important
 | 
			
		||||
 | 
			
		||||
===============================================================================
 | 
			
		||||
  Important: bind security update
 | 
			
		||||
===============================================================================
 | 
			
		||||
  Update ID : RHSA-2015:2655
 | 
			
		||||
    Release :
 | 
			
		||||
       Type : security
 | 
			
		||||
     Status : final
 | 
			
		||||
     Issued : 2015-09-03 01:00:00
 | 
			
		||||
    Updated : 2015-09-04 00:00:00
 | 
			
		||||
       Bugs : 1291176 - CVE-2015-8000 bind: responses with a malformed class attribute can trigger an assertion failure in db.c
 | 
			
		||||
       CVEs : CVE-2015-8000
 | 
			
		||||
            : CVE-2015-8001
 | 
			
		||||
Description : The Berkeley Internet Name Domain (BIND) is an implementation of
 | 
			
		||||
            : the Domain Name System (DNS) protocols. BIND
 | 
			
		||||
            : includes a DNS server (named); a resolver library
 | 
			
		||||
            : (routines for applications to use when interfacing
 | 
			
		||||
            : with DNS); and tools for verifying that the DNS
 | 
			
		||||
            : server is operating correctly.
 | 
			
		||||
            :
 | 
			
		||||
   Severity : Low
 | 
			
		||||
 | 
			
		||||
===============================================================================
 | 
			
		||||
  Moderate: bind security update
 | 
			
		||||
===============================================================================
 | 
			
		||||
  Update ID : RHSA-2016:0073
 | 
			
		||||
    Release :
 | 
			
		||||
       Type : security
 | 
			
		||||
     Status : final
 | 
			
		||||
     Issued : 2015-09-03 02:00:00
 | 
			
		||||
       Bugs : 1299364 - CVE-2015-8704 bind: specific APL data could trigger an INSIST in apl_42.c      CVEs : CVE-2015-8704
 | 
			
		||||
	        : CVE-2015-8705
 | 
			
		||||
Description : The Berkeley Internet Name Domain (BIND) is an implementation of
 | 
			
		||||
            : the Domain Name System (DNS) protocols. BIND
 | 
			
		||||
            : includes a DNS server (named); a resolver library
 | 
			
		||||
            : (routines for applications to use when interfacing
 | 
			
		||||
            : with DNS); and tools for verifying that the DNS
 | 
			
		||||
            : server is operating correctly.
 | 
			
		||||
            :
 | 
			
		||||
   Severity : Moderate
 | 
			
		||||
 | 
			
		||||
	`
 | 
			
		||||
	issued, _ := time.Parse("2006-01-02", "2015-09-03")
 | 
			
		||||
	updated, _ := time.Parse("2006-01-02", "2015-09-04")
 | 
			
		||||
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	r.Family = "redhat"
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
		out []distroAdvisoryCveIDs
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			stdout,
 | 
			
		||||
			[]distroAdvisoryCveIDs{
 | 
			
		||||
				{
 | 
			
		||||
					DistroAdvisory: models.DistroAdvisory{
 | 
			
		||||
						AdvisoryID: "RHSA-2015:1705",
 | 
			
		||||
						Severity:   "Important",
 | 
			
		||||
						Issued:     issued,
 | 
			
		||||
					},
 | 
			
		||||
					CveIDs: []string{"CVE-2015-5722"},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					DistroAdvisory: models.DistroAdvisory{
 | 
			
		||||
						AdvisoryID: "RHSA-2015:2655",
 | 
			
		||||
						Severity:   "Low",
 | 
			
		||||
						Issued:     issued,
 | 
			
		||||
						Updated:    updated,
 | 
			
		||||
					},
 | 
			
		||||
					CveIDs: []string{
 | 
			
		||||
						"CVE-2015-8000",
 | 
			
		||||
						"CVE-2015-8001",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					DistroAdvisory: models.DistroAdvisory{
 | 
			
		||||
						AdvisoryID: "RHSA-2016:0073",
 | 
			
		||||
						Severity:   "Moderate",
 | 
			
		||||
						Issued:     issued,
 | 
			
		||||
						Updated:    updated,
 | 
			
		||||
					},
 | 
			
		||||
					CveIDs: []string{
 | 
			
		||||
						"CVE-2015-8704",
 | 
			
		||||
						"CVE-2015-8705",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual, _ := r.parseYumUpdateinfo(tt.in)
 | 
			
		||||
		for i, advisoryCveIDs := range actual {
 | 
			
		||||
			if !reflect.DeepEqual(tt.out[i], advisoryCveIDs) {
 | 
			
		||||
				e := pp.Sprintf("%v", tt.out[i])
 | 
			
		||||
				a := pp.Sprintf("%v", advisoryCveIDs)
 | 
			
		||||
				t.Errorf("[%d] Alas is not same. \nexpected: %s\nactual: %s",
 | 
			
		||||
					i, e, a)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseYumUpdateinfoAmazon(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	r.Family = "amazon"
 | 
			
		||||
 | 
			
		||||
	issued, _ := time.Parse("2006-01-02", "2015-12-15")
 | 
			
		||||
	updated, _ := time.Parse("2006-01-02", "2015-12-16")
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
		out []distroAdvisoryCveIDs
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			`===============================================================================
 | 
			
		||||
  Amazon Linux AMI 2014.03 - ALAS-2016-644: medium priority package update for python-rsa
 | 
			
		||||
===============================================================================
 | 
			
		||||
  Update ID : ALAS-2016-644
 | 
			
		||||
    Release :
 | 
			
		||||
       Type : security
 | 
			
		||||
     Status : final
 | 
			
		||||
     Issued : 2015-12-15 13:30
 | 
			
		||||
       CVEs : CVE-2016-1494
 | 
			
		||||
Description : Package updates are available for Amazon Linux AMI that fix the
 | 
			
		||||
            : following vulnerabilities: CVE-2016-1494:
 | 
			
		||||
            :         1295869:
 | 
			
		||||
            : CVE-2016-1494 python-rsa: Signature forgery using
 | 
			
		||||
            : Bleichenbacher'06 attack
 | 
			
		||||
   Severity : medium
 | 
			
		||||
 | 
			
		||||
===============================================================================
 | 
			
		||||
  Amazon Linux AMI 2014.03 - ALAS-2015-614: medium priority package update for openssl
 | 
			
		||||
===============================================================================
 | 
			
		||||
  Update ID : ALAS-2015-614
 | 
			
		||||
    Release :
 | 
			
		||||
       Type : security
 | 
			
		||||
     Status : final
 | 
			
		||||
     Issued : 2015-12-15 10:00
 | 
			
		||||
    Updated : 2015-12-16 14:15       CVEs : CVE-2015-3194
 | 
			
		||||
            : CVE-2015-3195
 | 
			
		||||
            : CVE-2015-3196
 | 
			
		||||
Description : Package updates are available for Amazon Linux AMI that fix the
 | 
			
		||||
            : following vulnerabilities: CVE-2015-3196:
 | 
			
		||||
            :         1288326:
 | 
			
		||||
            : CVE-2015-3196 OpenSSL: Race condition handling PSK
 | 
			
		||||
            : identify hint A race condition flaw, leading to a
 | 
			
		||||
            : double free, was found in the way OpenSSL handled
 | 
			
		||||
            : pre-shared keys (PSKs). A remote attacker could
 | 
			
		||||
            : use this flaw to crash a multi-threaded SSL/TLS
 | 
			
		||||
            : client.
 | 
			
		||||
            :
 | 
			
		||||
   Severity : medium`,
 | 
			
		||||
 | 
			
		||||
			[]distroAdvisoryCveIDs{
 | 
			
		||||
				{
 | 
			
		||||
					DistroAdvisory: models.DistroAdvisory{
 | 
			
		||||
						AdvisoryID: "ALAS-2016-644",
 | 
			
		||||
						Severity:   "medium",
 | 
			
		||||
						Issued:     issued,
 | 
			
		||||
					},
 | 
			
		||||
					CveIDs: []string{"CVE-2016-1494"},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					DistroAdvisory: models.DistroAdvisory{
 | 
			
		||||
						AdvisoryID: "ALAS-2015-614",
 | 
			
		||||
						Severity:   "medium",
 | 
			
		||||
						Issued:     issued,
 | 
			
		||||
						Updated:    updated,
 | 
			
		||||
					},
 | 
			
		||||
					CveIDs: []string{
 | 
			
		||||
						"CVE-2015-3194",
 | 
			
		||||
						"CVE-2015-3195",
 | 
			
		||||
						"CVE-2015-3196",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual, _ := r.parseYumUpdateinfo(tt.in)
 | 
			
		||||
		for i, advisoryCveIDs := range actual {
 | 
			
		||||
			if !reflect.DeepEqual(tt.out[i], advisoryCveIDs) {
 | 
			
		||||
				e := pp.Sprintf("%v", tt.out[i])
 | 
			
		||||
				a := pp.Sprintf("%v", advisoryCveIDs)
 | 
			
		||||
				t.Errorf("[%d] Alas is not same. expected %s, actual %s",
 | 
			
		||||
					i, e, a)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseYumCheckUpdateLines(t *testing.T) {
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	r.Family = "centos"
 | 
			
		||||
	stdout := `Loaded plugins: changelog, fastestmirror, keys, protect-packages, protectbase, security
 | 
			
		||||
Loading mirror speeds from cached hostfile
 | 
			
		||||
 * base: mirror.fairway.ne.jp
 | 
			
		||||
 * epel: epel.mirror.srv.co.ge
 | 
			
		||||
 * extras: mirror.fairway.ne.jp
 | 
			
		||||
 * updates: mirror.fairway.ne.jp
 | 
			
		||||
0 packages excluded due to repository protections
 | 
			
		||||
 | 
			
		||||
audit-libs.x86_64              2.3.7-5.el6                   base
 | 
			
		||||
bash.x86_64                    4.1.2-33.el6_7.1              updates
 | 
			
		||||
	`
 | 
			
		||||
	r.Packages = []models.PackageInfo{
 | 
			
		||||
		{
 | 
			
		||||
			Name:    "audit-libs",
 | 
			
		||||
			Version: "2.3.6",
 | 
			
		||||
			Release: "4.el6",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			Name:    "bash",
 | 
			
		||||
			Version: "4.1.1",
 | 
			
		||||
			Release: "33",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
		out models.PackageInfoList
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			stdout,
 | 
			
		||||
			models.PackageInfoList{
 | 
			
		||||
				{
 | 
			
		||||
					Name:       "audit-libs",
 | 
			
		||||
					Version:    "2.3.6",
 | 
			
		||||
					Release:    "4.el6",
 | 
			
		||||
					NewVersion: "2.3.7",
 | 
			
		||||
					NewRelease: "5.el6",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:       "bash",
 | 
			
		||||
					Version:    "4.1.1",
 | 
			
		||||
					Release:    "33",
 | 
			
		||||
					NewVersion: "4.1.2",
 | 
			
		||||
					NewRelease: "33.el6_7.1",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		packInfoList, err := r.parseYumCheckUpdateLines(tt.in)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Error has occurred, err: %s\ntt.in: %v", err, tt.in)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		for i, ePackInfo := range tt.out {
 | 
			
		||||
			if !reflect.DeepEqual(ePackInfo, packInfoList[i]) {
 | 
			
		||||
				e := pp.Sprintf("%v", ePackInfo)
 | 
			
		||||
				a := pp.Sprintf("%v", packInfoList[i])
 | 
			
		||||
				t.Errorf("[%d] expected %s, actual %s", i, e, a)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseYumCheckUpdateLinesAmazon(t *testing.T) {
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	r.Family = "amzon"
 | 
			
		||||
	stdout := `Loaded plugins: priorities, update-motd, upgrade-helper
 | 
			
		||||
34 package(s) needed for security, out of 71 available
 | 
			
		||||
 | 
			
		||||
bind-libs.x86_64           32:9.8.2-0.37.rc1.45.amzn1      amzn-main
 | 
			
		||||
java-1.7.0-openjdk.x86_64  1:1.7.0.95-2.6.4.0.65.amzn1     amzn-main
 | 
			
		||||
if-not-architecture        100-200                         amzn-main
 | 
			
		||||
`
 | 
			
		||||
	r.Packages = []models.PackageInfo{
 | 
			
		||||
		{
 | 
			
		||||
			Name:    "bind-libs",
 | 
			
		||||
			Version: "32:9.8.0",
 | 
			
		||||
			Release: "0.33.rc1.45.amzn1",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			Name:    "java-1.7.0-openjdk",
 | 
			
		||||
			Version: "1:1.7.0.0",
 | 
			
		||||
			Release: "2.6.4.0.0.amzn1",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			Name:    "if-not-architecture",
 | 
			
		||||
			Version: "10",
 | 
			
		||||
			Release: "20",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
		out models.PackageInfoList
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			stdout,
 | 
			
		||||
			models.PackageInfoList{
 | 
			
		||||
				{
 | 
			
		||||
					Name:       "bind-libs",
 | 
			
		||||
					Version:    "32:9.8.0",
 | 
			
		||||
					Release:    "0.33.rc1.45.amzn1",
 | 
			
		||||
					NewVersion: "32:9.8.2",
 | 
			
		||||
					NewRelease: "0.37.rc1.45.amzn1",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:       "java-1.7.0-openjdk",
 | 
			
		||||
					Version:    "1:1.7.0.0",
 | 
			
		||||
					Release:    "2.6.4.0.0.amzn1",
 | 
			
		||||
					NewVersion: "1:1.7.0.95",
 | 
			
		||||
					NewRelease: "2.6.4.0.65.amzn1",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:       "if-not-architecture",
 | 
			
		||||
					Version:    "10",
 | 
			
		||||
					Release:    "20",
 | 
			
		||||
					NewVersion: "100",
 | 
			
		||||
					NewRelease: "200",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		packInfoList, err := r.parseYumCheckUpdateLines(tt.in)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Error has occurred, err: %s\ntt.in: %v", err, tt.in)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		for i, ePackInfo := range tt.out {
 | 
			
		||||
			if !reflect.DeepEqual(ePackInfo, packInfoList[i]) {
 | 
			
		||||
				e := pp.Sprintf("%v", ePackInfo)
 | 
			
		||||
				a := pp.Sprintf("%v", packInfoList[i])
 | 
			
		||||
				t.Errorf("[%d] expected %s, actual %s", i, e, a)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseYumUpdateinfoAmazonLinuxHeader(t *testing.T) {
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
		out models.DistroAdvisory
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			"Amazon Linux AMI 2014.03 - ALAS-2015-598: low priority package update for grep",
 | 
			
		||||
			models.DistroAdvisory{
 | 
			
		||||
				AdvisoryID: "ALAS-2015-598",
 | 
			
		||||
				Severity:   "low",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		a, _, _ := r.parseYumUpdateinfoHeaderAmazon(tt.in)
 | 
			
		||||
		if !reflect.DeepEqual(a, tt.out) {
 | 
			
		||||
			e := pp.Sprintf("%v", tt.out)
 | 
			
		||||
			a := pp.Sprintf("%v", a)
 | 
			
		||||
			t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseYumUpdateinfoListAvailable(t *testing.T) {
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	rhelStdout := `RHSA-2015:2315 Moderate/Sec.  NetworkManager-1:1.0.6-27.el7.x86_64
 | 
			
		||||
RHSA-2015:2315 Moderate/Sec.  NetworkManager-config-server-1:1.0.6-27.el7.x86_64
 | 
			
		||||
RHSA-2015:1705 Important/Sec. bind-libs-lite-32:9.9.4-18.el7_1.5.x86_64
 | 
			
		||||
RHSA-2016:0176 Critical/Sec.  glibc-2.17-106.el7_2.4.x86_64
 | 
			
		||||
RHSA-2015:2401 Low/Sec.       grub2-1:2.02-0.29.el7.x86_64
 | 
			
		||||
RHSA-2015:2401 Low/Sec.       grub2-tools-1:2.02-0.29.el7.x86_64
 | 
			
		||||
updateinfo list done`
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
		out []advisoryIDPacks
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			rhelStdout,
 | 
			
		||||
			[]advisoryIDPacks{
 | 
			
		||||
				{
 | 
			
		||||
					AdvisoryID: "RHSA-2015:2315",
 | 
			
		||||
					PackNames: []string{
 | 
			
		||||
						"NetworkManager",
 | 
			
		||||
						"NetworkManager-config-server",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					AdvisoryID: "RHSA-2015:1705",
 | 
			
		||||
					PackNames: []string{
 | 
			
		||||
						"bind-libs-lite",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					AdvisoryID: "RHSA-2016:0176",
 | 
			
		||||
					PackNames: []string{
 | 
			
		||||
						"glibc",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					AdvisoryID: "RHSA-2015:2401",
 | 
			
		||||
					PackNames: []string{
 | 
			
		||||
						"grub2",
 | 
			
		||||
						"grub2-tools",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual, err := r.parseYumUpdateinfoListAvailable(tt.in)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Error has occurred: %s", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for i := range actual {
 | 
			
		||||
			if !reflect.DeepEqual(actual[i], tt.out[i]) {
 | 
			
		||||
				e := pp.Sprintf("%v", tt.out)
 | 
			
		||||
				a := pp.Sprintf("%v", actual)
 | 
			
		||||
				t.Errorf("[%d] expected: %s\nactual: %s", i, e, a)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseYumUpdateinfoToGetUpdateID(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	var packagetests = []struct {
 | 
			
		||||
		in   string
 | 
			
		||||
		pack models.PackageInfo
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			"openssl	1.0.1e	30.el6.11",
 | 
			
		||||
			models.PackageInfo{
 | 
			
		||||
				Name:    "openssl",
 | 
			
		||||
				Version: "1.0.1e",
 | 
			
		||||
				Release: "30.el6.11",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range packagetests {
 | 
			
		||||
		p, _ := r.parseScanedPackagesLine(tt.in)
 | 
			
		||||
		if p.Name != tt.pack.Name {
 | 
			
		||||
			t.Errorf("name: expected %s, actual %s", tt.pack.Name, p.Name)
 | 
			
		||||
		}
 | 
			
		||||
		if p.Version != tt.pack.Version {
 | 
			
		||||
			t.Errorf("version: expected %s, actual %s", tt.pack.Version, p.Version)
 | 
			
		||||
		}
 | 
			
		||||
		if p.Release != tt.pack.Release {
 | 
			
		||||
			t.Errorf("release: expected %s, actual %s", tt.pack.Release, p.Release)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestExtractPackNameVerRel(t *testing.T) {
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
		out []string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			"openssh-server-6.2p2-8.45.amzn1.x86_64",
 | 
			
		||||
			[]string{"openssh-server", "6.2p2", "8.45.amzn1"},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"bind-libs-lite-32:9.9.4-29.el7_2.1.x86_64",
 | 
			
		||||
			[]string{"bind-libs-lite", "32:9.9.4", "29.el7_2.1"},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"glibc-2.17-106.el7_2.1.x86_64",
 | 
			
		||||
			[]string{"glibc", "2.17", "106.el7_2.1"},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		name, ver, rel := r.extractPackNameVerRel(tt.in)
 | 
			
		||||
		if tt.out[0] != name {
 | 
			
		||||
			t.Errorf("name: expected %s, actual %s", tt.out[0], name)
 | 
			
		||||
		}
 | 
			
		||||
		if tt.out[1] != ver {
 | 
			
		||||
			t.Errorf("ver: expected %s, actual %s", tt.out[1], ver)
 | 
			
		||||
		}
 | 
			
		||||
		if tt.out[2] != rel {
 | 
			
		||||
			t.Errorf("ver: expected %s, actual %s", tt.out[2], rel)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										209
									
								
								scan/serverapi.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								scan/serverapi.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,209 @@
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
	cve "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Log for localhsot
 | 
			
		||||
var Log *logrus.Entry
 | 
			
		||||
 | 
			
		||||
var servers []osTypeInterface
 | 
			
		||||
 | 
			
		||||
// Base Interface of redhat, debian
 | 
			
		||||
type osTypeInterface interface {
 | 
			
		||||
	setServerInfo(config.ServerInfo)
 | 
			
		||||
	getServerInfo() config.ServerInfo
 | 
			
		||||
	setDistributionInfo(string, string)
 | 
			
		||||
	checkRequiredPackagesInstalled() error
 | 
			
		||||
	scanPackages() error
 | 
			
		||||
	scanVulnByCpeName() error
 | 
			
		||||
	install() error
 | 
			
		||||
	convertToModel() (models.ScanResult, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// osPackages included by linux struct
 | 
			
		||||
type osPackages struct {
 | 
			
		||||
	// installed packages
 | 
			
		||||
	Packages models.PackageInfoList
 | 
			
		||||
 | 
			
		||||
	// unsecure packages
 | 
			
		||||
	UnsecurePackages CvePacksList
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *osPackages) setPackages(pi models.PackageInfoList) {
 | 
			
		||||
	p.Packages = pi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *osPackages) setUnsecurePackages(pi []CvePacksInfo) {
 | 
			
		||||
	p.UnsecurePackages = pi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CvePacksList have CvePacksInfo list, getter/setter, sortable methods.
 | 
			
		||||
type CvePacksList []CvePacksInfo
 | 
			
		||||
 | 
			
		||||
// CvePacksInfo hold the CVE information.
 | 
			
		||||
type CvePacksInfo struct {
 | 
			
		||||
	CveID            string
 | 
			
		||||
	CveDetail        cve.CveDetail
 | 
			
		||||
	Packs            []models.PackageInfo
 | 
			
		||||
	DistroAdvisories []models.DistroAdvisory // for Aamazon, RHEL
 | 
			
		||||
	CpeNames         []string
 | 
			
		||||
	//  CvssScore float64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindByCveID find by CVEID
 | 
			
		||||
func (s CvePacksList) FindByCveID(cveID string) (pi CvePacksInfo, found bool) {
 | 
			
		||||
	for _, p := range s {
 | 
			
		||||
		if cveID == p.CveID {
 | 
			
		||||
			return p, true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return CvePacksInfo{CveID: cveID}, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// immutable
 | 
			
		||||
func (s CvePacksList) set(cveID string, cvePacksInfo CvePacksInfo) CvePacksList {
 | 
			
		||||
	for i, p := range s {
 | 
			
		||||
		if cveID == p.CveID {
 | 
			
		||||
			s[i] = cvePacksInfo
 | 
			
		||||
			return s
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return append(s, cvePacksInfo)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Len implement Sort Interface
 | 
			
		||||
func (s CvePacksList) Len() int {
 | 
			
		||||
	return len(s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Swap implement Sort Interface
 | 
			
		||||
func (s CvePacksList) Swap(i, j int) {
 | 
			
		||||
	s[i], s[j] = s[j], s[i]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Less implement Sort Interface
 | 
			
		||||
func (s CvePacksList) Less(i, j int) bool {
 | 
			
		||||
	return s[i].CveDetail.CvssScore("en") > s[j].CveDetail.CvssScore("en")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detectOs(c config.ServerInfo) (osType osTypeInterface) {
 | 
			
		||||
	var itsMe bool
 | 
			
		||||
	itsMe, osType = detectDebian(c)
 | 
			
		||||
	if itsMe {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	itsMe, osType = detectRedhat(c)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// InitServers detect the kind of OS distribution of target servers
 | 
			
		||||
func InitServers(localLogger *logrus.Entry) (err error) {
 | 
			
		||||
	Log = localLogger
 | 
			
		||||
	if servers, err = detectServersOS(); err != nil {
 | 
			
		||||
		err = fmt.Errorf("Failed to detect OS")
 | 
			
		||||
	} else {
 | 
			
		||||
		Log.Debugf("%s", pp.Sprintf("%s", servers))
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detectServersOS() (osi []osTypeInterface, err error) {
 | 
			
		||||
	osTypeChan := make(chan osTypeInterface, len(config.Conf.Servers))
 | 
			
		||||
	defer close(osTypeChan)
 | 
			
		||||
	for _, s := range config.Conf.Servers {
 | 
			
		||||
		go func(s config.ServerInfo) {
 | 
			
		||||
			osTypeChan <- detectOs(s)
 | 
			
		||||
		}(s)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	timeout := time.After(60 * time.Second)
 | 
			
		||||
	for i := 0; i < len(config.Conf.Servers); i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case res := <-osTypeChan:
 | 
			
		||||
			osi = append(osi, res)
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			Log.Error("Timeout Occured while detecting OS.")
 | 
			
		||||
			err = fmt.Errorf("Timeout!")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Prepare installs requred packages to scan vulnerabilities.
 | 
			
		||||
func Prepare() []error {
 | 
			
		||||
	return parallelSSHExec(func(o osTypeInterface) error {
 | 
			
		||||
		if err := o.install(); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Scan scan
 | 
			
		||||
func Scan() []error {
 | 
			
		||||
	if len(servers) == 0 {
 | 
			
		||||
		return []error{fmt.Errorf("Not initialize yet.")}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Check required packages for scanning...")
 | 
			
		||||
	if errs := checkRequiredPackagesInstalled(); errs != nil {
 | 
			
		||||
		Log.Error("Please execute with [prepare] subcommand to install required packages before scanning")
 | 
			
		||||
		return errs
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Scanning vuluneable OS packages...")
 | 
			
		||||
	if errs := scanPackages(); errs != nil {
 | 
			
		||||
		return errs
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Scanning vulnerable software specified in CPE...")
 | 
			
		||||
	if errs := scanVulnByCpeName(); errs != nil {
 | 
			
		||||
		return errs
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func checkRequiredPackagesInstalled() []error {
 | 
			
		||||
	timeoutSec := 30 * 60
 | 
			
		||||
	return parallelSSHExec(func(o osTypeInterface) error {
 | 
			
		||||
		return o.checkRequiredPackagesInstalled()
 | 
			
		||||
	}, timeoutSec)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func scanPackages() []error {
 | 
			
		||||
	timeoutSec := 30 * 60
 | 
			
		||||
	return parallelSSHExec(func(o osTypeInterface) error {
 | 
			
		||||
		return o.scanPackages()
 | 
			
		||||
	}, timeoutSec)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// scanVulnByCpeName search vulnerabilities that specified in config file.
 | 
			
		||||
func scanVulnByCpeName() []error {
 | 
			
		||||
	timeoutSec := 30 * 60
 | 
			
		||||
	return parallelSSHExec(func(o osTypeInterface) error {
 | 
			
		||||
		return o.scanVulnByCpeName()
 | 
			
		||||
	}, timeoutSec)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetScanResults returns Scan Resutls
 | 
			
		||||
func GetScanResults() (results models.ScanResults, err error) {
 | 
			
		||||
	for _, s := range servers {
 | 
			
		||||
		r, err := s.convertToModel()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return results, fmt.Errorf("Failed converting to model: %s.", err)
 | 
			
		||||
		}
 | 
			
		||||
		results = append(results, r)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										47
									
								
								scan/serverapi_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								scan/serverapi_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import "testing"
 | 
			
		||||
 | 
			
		||||
func TestPackageCveInfosSetGet(t *testing.T) {
 | 
			
		||||
	var test = struct {
 | 
			
		||||
		in  []string
 | 
			
		||||
		out []string
 | 
			
		||||
	}{
 | 
			
		||||
		[]string{
 | 
			
		||||
			"CVE1",
 | 
			
		||||
			"CVE2",
 | 
			
		||||
			"CVE3",
 | 
			
		||||
			"CVE1",
 | 
			
		||||
			"CVE1",
 | 
			
		||||
			"CVE2",
 | 
			
		||||
			"CVE3",
 | 
			
		||||
		},
 | 
			
		||||
		[]string{
 | 
			
		||||
			"CVE1",
 | 
			
		||||
			"CVE2",
 | 
			
		||||
			"CVE3",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//  var ps packageCveInfos
 | 
			
		||||
	var ps CvePacksList
 | 
			
		||||
	for _, cid := range test.in {
 | 
			
		||||
		ps = ps.set(cid, CvePacksInfo{CveID: cid})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(test.out) != len(ps) {
 | 
			
		||||
		t.Errorf("length: expected %d, actual %d", len(test.out), len(ps))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, expectedCid := range test.out {
 | 
			
		||||
		if expectedCid != ps[i].CveID {
 | 
			
		||||
			t.Errorf("expected %s, actual %s", expectedCid, ps[i].CveID)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, cid := range test.in {
 | 
			
		||||
		p, _ := ps.FindByCveID(cid)
 | 
			
		||||
		if p.CveID != cid {
 | 
			
		||||
			t.Errorf("expected %s, actual %s", cid, p.CveID)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										311
									
								
								scan/sshutil.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										311
									
								
								scan/sshutil.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,311 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto/x509"
 | 
			
		||||
	"encoding/pem"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/crypto/ssh"
 | 
			
		||||
	"golang.org/x/crypto/ssh/agent"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/cenkalti/backoff"
 | 
			
		||||
	conf "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type sshResult struct {
 | 
			
		||||
	Host       string
 | 
			
		||||
	Port       string
 | 
			
		||||
	Stdout     string
 | 
			
		||||
	Stderr     string
 | 
			
		||||
	ExitStatus int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s sshResult) isSuccess(expectedStatusCodes ...int) bool {
 | 
			
		||||
	if len(expectedStatusCodes) == 0 {
 | 
			
		||||
		return s.ExitStatus == 0
 | 
			
		||||
	}
 | 
			
		||||
	for _, code := range expectedStatusCodes {
 | 
			
		||||
		if code == s.ExitStatus {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Sudo is Const value for sudo mode
 | 
			
		||||
const sudo = true
 | 
			
		||||
 | 
			
		||||
// NoSudo is Const value for normal user mode
 | 
			
		||||
const noSudo = false
 | 
			
		||||
 | 
			
		||||
func parallelSSHExec(fn func(osTypeInterface) error, timeoutSec ...int) (errs []error) {
 | 
			
		||||
	errChan := make(chan error, len(servers))
 | 
			
		||||
	defer close(errChan)
 | 
			
		||||
	for _, s := range servers {
 | 
			
		||||
		go func(s osTypeInterface) {
 | 
			
		||||
			if err := fn(s); err != nil {
 | 
			
		||||
				errChan <- fmt.Errorf("%s@%s:%s: %s",
 | 
			
		||||
					s.getServerInfo().User,
 | 
			
		||||
					s.getServerInfo().Host,
 | 
			
		||||
					s.getServerInfo().Port,
 | 
			
		||||
					err,
 | 
			
		||||
				)
 | 
			
		||||
			} else {
 | 
			
		||||
				errChan <- nil
 | 
			
		||||
			}
 | 
			
		||||
		}(s)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var timeout int
 | 
			
		||||
	if len(timeoutSec) == 0 {
 | 
			
		||||
		timeout = 10 * 60
 | 
			
		||||
	} else {
 | 
			
		||||
		timeout = timeoutSec[0]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := 0; i < len(servers); i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case err := <-errChan:
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				errs = append(errs, err)
 | 
			
		||||
			} else {
 | 
			
		||||
				logrus.Debug("Parallel SSH Success.")
 | 
			
		||||
			}
 | 
			
		||||
		case <-time.After(time.Duration(timeout) * time.Second):
 | 
			
		||||
			logrus.Errorf("Parallel SSH Timeout.")
 | 
			
		||||
			errs = append(errs, fmt.Errorf("Timed out!"))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func sshExec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (result sshResult) {
 | 
			
		||||
	// Setup Logger
 | 
			
		||||
	var logger *logrus.Entry
 | 
			
		||||
	if len(log) == 0 {
 | 
			
		||||
		level := logrus.InfoLevel
 | 
			
		||||
		if conf.Conf.Debug == true {
 | 
			
		||||
			level = logrus.DebugLevel
 | 
			
		||||
		}
 | 
			
		||||
		l := &logrus.Logger{
 | 
			
		||||
			Out:       os.Stderr,
 | 
			
		||||
			Formatter: new(logrus.TextFormatter),
 | 
			
		||||
			Hooks:     make(logrus.LevelHooks),
 | 
			
		||||
			Level:     level,
 | 
			
		||||
		}
 | 
			
		||||
		logger = logrus.NewEntry(l)
 | 
			
		||||
	} else {
 | 
			
		||||
		logger = log[0]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var err error
 | 
			
		||||
	if sudo && c.User != "root" {
 | 
			
		||||
		switch {
 | 
			
		||||
		case c.SudoOpt.ExecBySudo:
 | 
			
		||||
			cmd = fmt.Sprintf("echo %s | sudo -S %s", c.Password, cmd)
 | 
			
		||||
		case c.SudoOpt.ExecBySudoSh:
 | 
			
		||||
			cmd = fmt.Sprintf("echo %s | sudo sh -c '%s'", c.Password, cmd)
 | 
			
		||||
		default:
 | 
			
		||||
			logger.Panicf("sudoOpt is invalid. SudoOpt: %v", c.SudoOpt)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// set pipefail option.
 | 
			
		||||
	// http://unix.stackexchange.com/questions/14270/get-exit-status-of-process-thats-piped-to-another
 | 
			
		||||
	cmd = fmt.Sprintf("set -o pipefail; %s", cmd)
 | 
			
		||||
	logger.Debugf("Command: %s", strings.Replace(cmd, "\n", "", -1))
 | 
			
		||||
 | 
			
		||||
	var client *ssh.Client
 | 
			
		||||
	client, err = sshConnect(c)
 | 
			
		||||
	defer client.Close()
 | 
			
		||||
 | 
			
		||||
	var session *ssh.Session
 | 
			
		||||
	if session, err = client.NewSession(); err != nil {
 | 
			
		||||
		logger.Errorf("Failed to new session. err: %s, c: %s",
 | 
			
		||||
			err,
 | 
			
		||||
			pp.Sprintf("%v", c))
 | 
			
		||||
		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, 120, modes); err != nil {
 | 
			
		||||
		logger.Errorf("Failed to request for pseudo terminal. err: %s, c: %s",
 | 
			
		||||
			err,
 | 
			
		||||
			pp.Sprintf("%v", c))
 | 
			
		||||
 | 
			
		||||
		result.ExitStatus = 999
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var stdoutBuf, stderrBuf bytes.Buffer
 | 
			
		||||
	session.Stdout = &stdoutBuf
 | 
			
		||||
	session.Stderr = &stderrBuf
 | 
			
		||||
 | 
			
		||||
	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.Host = c.Host
 | 
			
		||||
	result.Port = c.Port
 | 
			
		||||
 | 
			
		||||
	logger.Debugf(
 | 
			
		||||
		"SSH executed. cmd: %s, status: %#v\nstdout: \n%s\nstderr: \n%s",
 | 
			
		||||
		cmd, err, result.Stdout, result.Stderr)
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getAgentAuth() (auth ssh.AuthMethod, ok bool) {
 | 
			
		||||
	if sock := os.Getenv("SSH_AUTH_SOCK"); len(sock) > 0 {
 | 
			
		||||
		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},
 | 
			
		||||
		}
 | 
			
		||||
		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 {
 | 
			
		||||
		logrus.Fatalf("Faild to add keyAuth. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Password != "" {
 | 
			
		||||
		auths = append(auths, ssh.Password(c.Password))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// http://blog.ralch.com/tutorial/golang-ssh-connection/
 | 
			
		||||
	config := &ssh.ClientConfig{
 | 
			
		||||
		User: c.User,
 | 
			
		||||
		Auth: auths,
 | 
			
		||||
	}
 | 
			
		||||
	//  log.Debugf("config: %s", pp.Sprintf("%v", config))
 | 
			
		||||
 | 
			
		||||
	notifyFunc := func(e error, t time.Duration) {
 | 
			
		||||
		logrus.Warnf("Faild to ssh %s@%s:%s. err: %s, Retrying in %s...",
 | 
			
		||||
			c.User, c.Host, c.Port, e, t)
 | 
			
		||||
		logrus.Debugf("sshConInfo: %s", pp.Sprintf("%v", c))
 | 
			
		||||
	}
 | 
			
		||||
	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, fmt.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, fmt.Errorf("rtop: unsupported key type %q", block.Type)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										68
									
								
								util/logutil.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								util/logutil.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package util
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/rifflock/lfshook"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	formatter "github.com/kotakanbe/logrus-prefixed-formatter"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// NewCustomLogger creates logrus
 | 
			
		||||
func NewCustomLogger(c config.ServerInfo) *logrus.Entry {
 | 
			
		||||
	log := logrus.New()
 | 
			
		||||
	log.Formatter = &formatter.TextFormatter{MsgAnsiColor: c.LogMsgAnsiColor}
 | 
			
		||||
	log.Out = os.Stderr
 | 
			
		||||
	log.Level = logrus.InfoLevel
 | 
			
		||||
	if config.Conf.Debug {
 | 
			
		||||
		log.Level = logrus.DebugLevel
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// File output
 | 
			
		||||
	logDir := "/var/log/vuls"
 | 
			
		||||
	if _, err := os.Stat(logDir); os.IsNotExist(err) {
 | 
			
		||||
		if err := os.Mkdir(logDir, 0666); err != nil {
 | 
			
		||||
			logrus.Errorf("Failed to create log directory: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	whereami := "localhost"
 | 
			
		||||
	if 0 < len(c.ServerName) {
 | 
			
		||||
		whereami = fmt.Sprintf("%s:%s", c.ServerName, c.Port)
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := os.Stat(logDir); err == nil {
 | 
			
		||||
		path := fmt.Sprintf("%s/%s.log", logDir, whereami)
 | 
			
		||||
		log.Hooks.Add(lfshook.NewHook(lfshook.PathMap{
 | 
			
		||||
			logrus.DebugLevel: path,
 | 
			
		||||
			logrus.InfoLevel:  path,
 | 
			
		||||
			logrus.WarnLevel:  path,
 | 
			
		||||
			logrus.ErrorLevel: path,
 | 
			
		||||
			logrus.FatalLevel: path,
 | 
			
		||||
			logrus.PanicLevel: path,
 | 
			
		||||
		}))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fields := logrus.Fields{"prefix": whereami}
 | 
			
		||||
	return log.WithFields(fields)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										120
									
								
								util/util.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								util/util.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,120 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package util
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GenWorkers generates goroutine
 | 
			
		||||
// http://qiita.com/na-o-ys/items/65373132b1c5bc973cca
 | 
			
		||||
func GenWorkers(num int) chan<- func() {
 | 
			
		||||
	tasks := make(chan func())
 | 
			
		||||
	for i := 0; i < num; i++ {
 | 
			
		||||
		go func() {
 | 
			
		||||
			for f := range tasks {
 | 
			
		||||
				f()
 | 
			
		||||
			}
 | 
			
		||||
		}()
 | 
			
		||||
	}
 | 
			
		||||
	return tasks
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AppendIfMissing append to the slice if missing
 | 
			
		||||
func AppendIfMissing(slice []string, s string) []string {
 | 
			
		||||
	for _, ele := range slice {
 | 
			
		||||
		if ele == s {
 | 
			
		||||
			return slice
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return append(slice, s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// URLPathJoin make URL
 | 
			
		||||
func URLPathJoin(baseURL string, paths ...string) (string, error) {
 | 
			
		||||
	baseURL = strings.TrimSuffix(baseURL, "/")
 | 
			
		||||
	trimedPaths := []string{}
 | 
			
		||||
	for _, path := range paths {
 | 
			
		||||
		trimed := strings.Trim(path, " /")
 | 
			
		||||
		if len(trimed) != 0 {
 | 
			
		||||
			trimedPaths = append(trimedPaths, trimed)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	var url *url.URL
 | 
			
		||||
	url, err := url.Parse(baseURL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	url.Path += strings.Join(trimedPaths, "/")
 | 
			
		||||
	return url.String(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// URLPathParamJoin make URL
 | 
			
		||||
func URLPathParamJoin(baseURL string, paths []string, params map[string]string) (string, error) {
 | 
			
		||||
	urlPath, err := URLPathJoin(baseURL, paths...)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	u, err := url.Parse(urlPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	parameters := url.Values{}
 | 
			
		||||
	for key := range params {
 | 
			
		||||
		parameters.Add(key, params[key])
 | 
			
		||||
	}
 | 
			
		||||
	u.RawQuery = parameters.Encode()
 | 
			
		||||
	return u.String(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ProxyEnv returns shell environment variables to set proxy
 | 
			
		||||
func ProxyEnv() string {
 | 
			
		||||
	httpProxyEnv := "env"
 | 
			
		||||
	keys := []string{
 | 
			
		||||
		"http_proxy",
 | 
			
		||||
		"https_proxy",
 | 
			
		||||
		"HTTP_PROXY",
 | 
			
		||||
		"HTTPS_PROXY",
 | 
			
		||||
	}
 | 
			
		||||
	for _, key := range keys {
 | 
			
		||||
		httpProxyEnv += fmt.Sprintf(
 | 
			
		||||
			` %s="%s"`, key, config.Conf.HTTPProxy)
 | 
			
		||||
	}
 | 
			
		||||
	return httpProxyEnv
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PrependProxyEnv prepends proxy enviroment variable
 | 
			
		||||
func PrependProxyEnv(cmd string) string {
 | 
			
		||||
	if config.Conf.HTTPProxy == "" {
 | 
			
		||||
		return cmd
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s %s", ProxyEnv(), cmd)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//  func unixtime(s string) (time.Time, error) {
 | 
			
		||||
//      i, err := strconv.ParseInt(s, 10, 64)
 | 
			
		||||
//      if err != nil {
 | 
			
		||||
//          return time.Time{}, err
 | 
			
		||||
//      }
 | 
			
		||||
//      return time.Unix(i, 0), nil
 | 
			
		||||
//  }
 | 
			
		||||
							
								
								
									
										133
									
								
								util/util_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								util/util_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,133 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package util
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestUrlJoin(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  []string
 | 
			
		||||
		out string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			[]string{
 | 
			
		||||
				"http://hoge.com:8080",
 | 
			
		||||
				"status",
 | 
			
		||||
			},
 | 
			
		||||
			"http://hoge.com:8080/status",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			[]string{
 | 
			
		||||
				"http://hoge.com:8080/",
 | 
			
		||||
				"status",
 | 
			
		||||
			},
 | 
			
		||||
			"http://hoge.com:8080/status",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			[]string{
 | 
			
		||||
				"http://hoge.com:8080",
 | 
			
		||||
				"/status",
 | 
			
		||||
			},
 | 
			
		||||
			"http://hoge.com:8080/status",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			[]string{
 | 
			
		||||
				"http://hoge.com:8080/",
 | 
			
		||||
				"/status",
 | 
			
		||||
			},
 | 
			
		||||
			"http://hoge.com:8080/status",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			[]string{
 | 
			
		||||
				"http://hoge.com:8080/",
 | 
			
		||||
				"/status",
 | 
			
		||||
			},
 | 
			
		||||
			"http://hoge.com:8080/status",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			[]string{
 | 
			
		||||
				"http://hoge.com:8080/",
 | 
			
		||||
				"",
 | 
			
		||||
				"hega",
 | 
			
		||||
				"",
 | 
			
		||||
			},
 | 
			
		||||
			"http://hoge.com:8080/hega",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			[]string{
 | 
			
		||||
				"http://hoge.com:8080/",
 | 
			
		||||
				"status",
 | 
			
		||||
				"/fuga",
 | 
			
		||||
			},
 | 
			
		||||
			"http://hoge.com:8080/status/fuga",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		baseurl := tt.in[0]
 | 
			
		||||
		paths := tt.in[1:]
 | 
			
		||||
		actual, err := URLPathJoin(baseurl, paths...)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("\nunexpected error occurred, err: %s,\ninput:%#v\nexpected: %s\n  actual: %s", err, tt.in, tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
		if actual != tt.out {
 | 
			
		||||
			t.Errorf("\ninput:%#v\nexpected: %s\n  actual: %s", tt.in, tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPrependHTTPProxyEnv(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  []string
 | 
			
		||||
		out string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			[]string{
 | 
			
		||||
				"http://proxy.co.jp:8080",
 | 
			
		||||
				"yum check-update",
 | 
			
		||||
			},
 | 
			
		||||
			`env http_proxy="http://proxy.co.jp:8080" https_proxy="http://proxy.co.jp:8080" HTTP_PROXY="http://proxy.co.jp:8080" HTTPS_PROXY="http://proxy.co.jp:8080" yum check-update`,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			[]string{
 | 
			
		||||
				"http://proxy.co.jp:8080",
 | 
			
		||||
				"",
 | 
			
		||||
			},
 | 
			
		||||
			`env http_proxy="http://proxy.co.jp:8080" https_proxy="http://proxy.co.jp:8080" HTTP_PROXY="http://proxy.co.jp:8080" HTTPS_PROXY="http://proxy.co.jp:8080" `,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			[]string{
 | 
			
		||||
				"",
 | 
			
		||||
				"yum check-update",
 | 
			
		||||
			},
 | 
			
		||||
			`yum check-update`,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		config.Conf.HTTPProxy = tt.in[0]
 | 
			
		||||
		actual := PrependProxyEnv(tt.in[1])
 | 
			
		||||
		if actual != tt.out {
 | 
			
		||||
			t.Errorf("\nexpected: %s\n  actual: %s", tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								version.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								version.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
// Name.
 | 
			
		||||
const Name string = "vuls"
 | 
			
		||||
 | 
			
		||||
// Version.
 | 
			
		||||
const Version string = "0.1.0"
 | 
			
		||||
		Reference in New Issue
	
	Block a user