From ce6a4231ef3139336ef1099f93f5128c681ddf48 Mon Sep 17 00:00:00 2001 From: Kota Kanbe Date: Tue, 7 Mar 2017 18:09:10 +0900 Subject: [PATCH 1/2] Deprecate prepare subcommand to minimize the root authority defined by /etc/sudoers From 688cfd687277140aad2bf1a4055f5ed216ac2d41 Mon Sep 17 00:00:00 2001 From: Kota Kanbe Date: Wed, 8 Mar 2017 13:40:44 +0900 Subject: [PATCH 2/2] Deprecate prepare subcommand to minimize the root authority #375 --- README.ja.md | 104 +++++++++++------------ README.md | 109 ++++++++++++------------ commands/configtest.go | 15 +++- commands/prepare.go | 185 ----------------------------------------- commands/scan.go | 3 - main.go | 1 - scan/base.go | 5 -- scan/debian.go | 52 ++---------- scan/executil.go | 3 - scan/freebsd.go | 8 -- scan/redhat.go | 126 +++++++++++++++------------- scan/serverapi.go | 141 ++++--------------------------- scan/unknownDistro.go | 8 -- util/util.go | 2 +- util/util_test.go | 4 +- 15 files changed, 218 insertions(+), 548 deletions(-) delete mode 100644 commands/prepare.go diff --git a/README.ja.md b/README.ja.md index c04f713f..8236052b 100644 --- a/README.ja.md +++ b/README.ja.md @@ -87,7 +87,7 @@ Hello Vulsチュートリアルでは手動でのセットアップ方法で説 1. go-cve-dictionaryをデプロイ 1. Vulsをデプロイ 1. 設定 -1. Prepare +1. 設定ファイルと、スキャン対象サーバの設定のチェック 1. Scan 1. Reporting 1. TUI(Terminal-Based User Interface)で結果を参照する @@ -216,15 +216,14 @@ port = "22" user = "ec2-user" keyPath = "/home/ec2-user/.ssh/id_rsa" +``` + +## Step7. Check config.toml and settings on the server before scanning + +``` $ vuls configtest ``` - -## Step7. Setting up target servers for Vuls - -``` -$ vuls prepare -``` -詳細は [Usage: Prepare](https://github.com/future-architect/vuls#usage-prepare) を参照 +詳細は [Usage: configtest](#usage-configtest) を参照 ## Step8. Start Scanning @@ -327,7 +326,7 @@ $ vuls tui # Architecture -## A. Scan via SSH Mode +## A. Scan via SSH Mode (Remote Scan Mode) ![Vuls-Architecture](img/vuls-architecture.png) @@ -585,8 +584,6 @@ host = "172.31.4.82" # Usage: Configtest -configtestサブコマンドは、config.tomlで定義されたサーバ/コンテナに対してSSH可能かどうかをチェックする。 - ``` $ vuls configtest --help configtest: @@ -595,6 +592,7 @@ configtest: [-log-dir=/path/to/log] [-ask-key-password] [-ssh-external] + [-http-proxy=http://192.168.0.1:8080] [-debug] [SERVER]... @@ -604,66 +602,70 @@ configtest: /path/to/toml (default "/Users/kotakanbe/go/src/github.com/future-architect/vuls/config.toml") -debug debug mode + -http-proxy string + http://proxy-url:port (default: empty) -log-dir string /path/to/log (default "/var/log/vuls") -ssh-external Use external ssh command. Default: Use the Go native implementation ``` -また、スキャン対象サーバに対してパスワードなしでSUDO可能な状態かもチェックする。 +configtestサブコマンドは以下をチェックする +- config.tomlで定義されたサーバ/コンテナに対してSSH可能かどうか +- スキャン対象のサーバ上に依存パッケーがインストールされているか +- /etc/sudoers -スキャン対象サーバ上の`/etc/sudoers`のサンプル +## Dependencies on Target Servers -- CentOS, RHEL, Amazon Linux -``` -vuls ALL=(root) NOPASSWD: /usr/bin/yum -``` -- Ubuntu, Debian, Raspbian -``` -vuls ALL=(root) NOPASSWD: /usr/bin/apt-get -``` -- Amazon Linux, FreeBSDはRoot権限なしでスキャン可能 - ----- - -# Usage: Prepare - -Prepareサブコマンドは、Vuls内部で利用する以下のパッケージをスキャン対象サーバにインストールする。 +スキャンするためには、下記のパッケージが必要なので、手動かまたはAnsibleなどのツールで事前にインストールする必要がある。 | Distribution| Release | Requirements | |:------------|-------------------:|:-------------| | Ubuntu | 12, 14, 16| - | | Debian | 7, 8| aptitude | -| CentOS | 5| yum-changelog | | CentOS | 6, 7| yum-plugin-changelog | -| Amazon | All | - | -| RHEL | 5, 6, 7 | - | +| Amazon | All | - | +| RHEL | 5 | yum-security | +| RHEL | 6, 7 | - | | FreeBSD | 10 | - | | Raspbian | Wheezy, Jessie | - | +## Check /etc/sudoers -``` -$ vuls prepare -help -prepare: - prepare - [-config=/path/to/config.toml] - [-log-dir=/path/to/log] - [-ask-key-password] - [-debug] - [-ssh-external] +スキャン対象サーバに対してパスワードなしでSUDO可能な状態かもチェックする。 +スキャン対象サーバ上の`/etc/sudoers`のサンプル - [SERVER]... - -ask-key-password - Ask ssh privatekey password before scanning - -config string - /path/to/toml (default "$PWD/config.toml") - -debug - debug mode - -log-dir string - /path/to/log (default "/var/log/vuls") - -ssh-external - Use external ssh command. Default: Use the Go native implementation +- CentOS ``` +vuls ALL=(ALL) NOPASSWD:/usr/bin/yum --changelog --assumeno update * +Defaults:vuls env_keep="http_proxy https_proxy HTTP_PROXY HTTPS_PROXY" +``` + +- RHEL 5 +``` +vuls ALL=(ALL) NOPASSWD:/usr/bin/yum --color=never repolist, /usr/bin/yum --color=never list-security --security, /usr/bin/yum --color=never check-update, /usr/bin/yum --color=never info-security +Defaults:vuls env_keep="http_proxy https_proxy HTTP_PROXY HTTPS_PROXY" +``` + +- RHEL 6, 7 +``` +vuls ALL=(ALL) NOPASSWD:/usr/bin/yum --color=never repolist, /usr/bin/yum --color=never --security updateinfo list updates, /usr/bin/yum --color=never check-update, /usr/bin/yum --color=never --security updateinfo updates +Defaults:vuls env_keep="http_proxy https_proxy HTTP_PROXY HTTPS_PROXY" +``` + +- Debian +``` +vuls ALL=(ALL) NOPASSWD: /usr/bin/apt-get update +Defaults:vuls env_keep="http_proxy https_proxy HTTP_PROXY HTTPS_PROXY" +``` + +- Ubuntu/Raspbian +``` +vuls ALL=(ALL) NOPASSWD: /usr/bin/apt-get update +Defaults:vuls env_keep="http_proxy https_proxy HTTP_PROXY HTTPS_PROXY" +``` + +- Amazon Linux, FreeBSDは今のところRoot権限なしでスキャン可能 ---- diff --git a/README.md b/README.md index 6135ae00..2bbd2662 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ This can be done in the following steps. 1. Deploy go-cve-dictionary 1. Deploy Vuls 1. Configuration -1. Prepare +1. Check config.toml and settings on the server before scanning 1. Scan 1. Reporting 1. TUI(Terminal-Based User Interface) @@ -211,15 +211,15 @@ port = "22" user = "ec2-user" keyPath = "/home/ec2-user/.ssh/id_rsa" +``` + +## Step7. Check config.toml and settings on the server before scanning + +``` $ vuls configtest ``` -## Step7. Setting up target servers for Vuls - -``` -$ vuls prepare -``` -see [Usage: Prepare](https://github.com/future-architect/vuls#usage-prepare) +see [Usage: configtest](#usage-configtest) ## Step8. Start Scanning @@ -325,7 +325,7 @@ see https://github.com/future-architect/vuls/tree/master/setup/docker # Architecture -## A. Scan via SSH Mode +## A. Scan via SSH Mode (Remote Scan Mode) ![Vuls-Architecture](img/vuls-architecture.png) @@ -589,7 +589,6 @@ You can customize your configuration using this template. # Usage: Configtest -Configtest subcommand check if vuls is able to connect via ssh to servers/containers defined in the config.toml. ``` $ vuls configtest --help configtest: @@ -607,72 +606,72 @@ configtest: /path/to/toml (default "/Users/kotakanbe/go/src/github.com/future-architect/vuls/config.toml") -debug debug mode + -http-proxy string + http://proxy-url:port (default: empty) -log-dir string /path/to/log (default "/var/log/vuls") -ssh-external Use external ssh command. Default: Use the Go native implementation ``` -And also, configtest subcommand checks sudo settings on target servers whether Vuls is able to SUDO with nopassword via SSH. +The configtest subcommand checks the following +- Whether vuls is able to connect via ssh to servers/containers defined in the config.toml +- Whether Dependent package is installed on the scan target server +- Check /etc/sudoers -Example of /etc/sudoers on target servers -- CentOS and RHEL -``` -vuls ALL=(root) NOPASSWD: /usr/bin/yum -``` -- Ubuntu, Debian and Raspbian -``` -vuls ALL=(root) NOPASSWD: /usr/bin/apt-get -``` -- It is possible to scan without root privilege for Amazon Linux, FreeBSD. +## Dependencies on Target Servers - - ----- - -# Usage: Prepare - -Prepare subcommand installs required packages on each server. +In order to scan, the following dependencies are required, so you need to install them manually or with tools such as Ansible. | Distribution| Release | Requirements | |:------------|-------------------:|:-------------| | Ubuntu | 12, 14, 16| - | | Debian | 7, 8| aptitude | -| CentOS | 5| yum-changelog | | CentOS | 6, 7| yum-plugin-changelog | -| Amazon | All | - | -| RHEL | 5, 6, 7 | - | +| Amazon | All | - | +| RHEL | 5 | yum-security | +| RHEL | 6, 7 | - | | FreeBSD | 10 | - | | Raspbian | Wheezy, Jessie | - | +## Check /etc/sudoers -``` -$ vuls prepare -help -prepare: - prepare - [-config=/path/to/config.toml] - [-log-dir=/path/to/log] - [-ask-key-password] - [-assume-yes] - [-debug] - [-ssh-external] +The configtest subcommand checks sudo settings on target servers whether Vuls is able to SUDO with nopassword via SSH. - [SERVER]... - -ask-key-password - Ask ssh privatekey password before scanning - -ask-sudo-password - [Deprecated] THIS OPTION WAS REMOVED FOR SECURITY REASONS. Define NOPASSWD in /etc/sudoers on target servers and use SSH key-based authentication - -assume-yes - Assume any dependencies should be installed - -config string - /path/to/toml (default "$PWD/config.toml") - -debug - debug mode - -log-dir string - /path/to/log (default "/var/log/vuls") - -ssh-external - Use external ssh command. Default: Use the Go native implementation +Example of /etc/sudoers on target servers + +- CentOS ``` +vuls ALL=(ALL) NOPASSWD:/usr/bin/yum --changelog --assumeno update * +Defaults:vuls env_keep="http_proxy https_proxy HTTP_PROXY HTTPS_PROXY" +``` + +- RHEL 5 +``` +vuls ALL=(ALL) NOPASSWD:/usr/bin/yum --color=never repolist, /usr/bin/yum --color=never list-security --security, /usr/bin/yum --color=never check-update, /usr/bin/yum --color=never info-security +Defaults:vuls env_keep="http_proxy https_proxy HTTP_PROXY HTTPS_PROXY" +``` + +- RHEL 6, 7 +``` +vuls ALL=(ALL) NOPASSWD:/usr/bin/yum --color=never repolist, /usr/bin/yum --color=never --security updateinfo list updates, /usr/bin/yum --color=never check-update, /usr/bin/yum --color=never --security updateinfo updates +Defaults:vuls env_keep="http_proxy https_proxy HTTP_PROXY HTTPS_PROXY" +``` + +- Debian +``` +vuls ALL=(ALL) NOPASSWD: /usr/bin/apt-get update +Defaults:vuls env_keep="http_proxy https_proxy HTTP_PROXY HTTPS_PROXY" +``` + +- Ubuntu/Raspbian +``` +vuls ALL=(ALL) NOPASSWD: /usr/bin/apt-get update +Defaults:vuls env_keep="http_proxy https_proxy HTTP_PROXY HTTPS_PROXY" +``` + +- On Amazon Linux, FreeBSD, it is possible to scan without root privilege for now. + ---- # Usage: Scan diff --git a/commands/configtest.go b/commands/configtest.go index 4a4f5da2..049e9cf8 100644 --- a/commands/configtest.go +++ b/commands/configtest.go @@ -36,6 +36,7 @@ type ConfigtestCmd struct { logDir string askKeyPassword bool sshExternal bool + httpProxy string debug bool } @@ -54,6 +55,7 @@ func (*ConfigtestCmd) Usage() string { [-log-dir=/path/to/log] [-ask-key-password] [-ssh-external] + [-http-proxy=http://192.168.0.1:8080] [-debug] [SERVER]... @@ -78,6 +80,13 @@ func (p *ConfigtestCmd) SetFlags(f *flag.FlagSet) { "Ask ssh privatekey password before scanning", ) + f.StringVar( + &p.httpProxy, + "http-proxy", + "", + "http://proxy-url:port (default: empty)", + ) + f.BoolVar( &p.sshExternal, "ssh-external", @@ -108,6 +117,7 @@ func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interfa return subcommands.ExitUsageError } c.Conf.SSHExternal = p.sshExternal + c.Conf.HTTPProxy = p.httpProxy var servernames []string if 0 < len(f.Args()) { @@ -144,7 +154,10 @@ func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interfa return subcommands.ExitFailure } - util.Log.Info("Checking sudo configuration...") + util.Log.Info("Checking dependendies...") + scan.CheckDependencies() + + util.Log.Info("Checking sudo settings...") scan.CheckIfSudoNoPasswd() scan.PrintSSHableServerNames() diff --git a/commands/prepare.go b/commands/prepare.go deleted file mode 100644 index b582fba9..00000000 --- a/commands/prepare.go +++ /dev/null @@ -1,185 +0,0 @@ -/* 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 . -*/ - -package commands - -import ( - "context" - "flag" - "os" - "path/filepath" - - c "github.com/future-architect/vuls/config" - "github.com/future-architect/vuls/scan" - "github.com/future-architect/vuls/util" - "github.com/google/subcommands" -) - -// PrepareCmd is Subcommand of host discovery mode -type PrepareCmd struct { - debug bool - configPath string - logDir string - - askSudoPassword bool - askKeyPassword bool - - sshExternal bool - assumeYes bool -} - -// Name return subcommand name -func (*PrepareCmd) Name() string { return "prepare" } - -// Synopsis return synopsis -func (*PrepareCmd) Synopsis() string { - return `Install required packages to scan. - CentOS: yum-plugin-security, yum-plugin-changelog - Amazon: None - RHEL: None - Ubuntu: None - Debian: aptitude - - ` -} - -// Usage return usage -func (*PrepareCmd) Usage() string { - return `prepare: - prepare - [-config=/path/to/config.toml] - [-log-dir=/path/to/log] - [-ask-key-password] - [-assume-yes] - [-debug] - [-ssh-external] - - [SERVER]... -` -} - -// SetFlags set flag -func (p *PrepareCmd) SetFlags(f *flag.FlagSet) { - - f.BoolVar(&p.debug, "debug", false, "debug mode") - - wd, _ := os.Getwd() - - defaultConfPath := filepath.Join(wd, "config.toml") - f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml") - - defaultLogDir := util.GetDefaultLogDir() - f.StringVar(&p.logDir, "log-dir", defaultLogDir, "/path/to/log") - - f.BoolVar( - &p.askKeyPassword, - "ask-key-password", - false, - "Ask ssh privatekey password before scanning", - ) - - f.BoolVar( - &p.askSudoPassword, - "ask-sudo-password", - false, - "[Deprecated] THIS OPTION WAS REMOVED FOR SECURITY REASONS. Define NOPASSWD in /etc/sudoers on target servers and use SSH key-based authentication", - ) - - f.BoolVar( - &p.sshExternal, - "ssh-external", - false, - "Use external ssh command. Default: Use the Go native implementation") - - f.BoolVar( - &p.assumeYes, - "assume-yes", - false, - "Assume any dependencies should be installed") - -} - -// Execute execute -func (p *PrepareCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { - c.Conf.LogDir = p.logDir - c.Conf.Debug = p.debug - util.Log = util.NewCustomLogger(c.ServerInfo{}) - - var keyPass string - var err error - if p.askKeyPassword { - prompt := "SSH key password: " - if keyPass, err = getPasswd(prompt); err != nil { - util.Log.Error(err) - return subcommands.ExitFailure - } - } - if p.askSudoPassword { - util.Log.Errorf("[Deprecated] -ask-sudo-password WAS REMOVED FOR SECURITY REASONS. Define NOPASSWD in /etc/sudoers on target servers and use SSH key-based authentication") - return subcommands.ExitFailure - } - - err = c.Load(p.configPath, keyPass) - if err != nil { - util.Log.Errorf("Error loading %s, %s", p.configPath, err) - return subcommands.ExitUsageError - } - - util.Log.Infof("Start Preparing (config: %s)", p.configPath) - 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 { - util.Log.Errorf("%s is not in config", arg) - return subcommands.ExitUsageError - } - } - if 0 < len(f.Args()) { - c.Conf.Servers = target - } - - c.Conf.SSHExternal = p.sshExternal - c.Conf.AssumeYes = p.assumeYes - - util.Log.Info("Validating config...") - if !c.Conf.ValidateOnPrepare() { - return subcommands.ExitUsageError - } - - util.Log.Info("Detecting OS... ") - if err := scan.InitServers(); err != nil { - util.Log.Errorf("Failed to init servers: %s", err) - return subcommands.ExitFailure - } - - util.Log.Info("Checking sudo configuration... ") - scan.CheckIfSudoNoPasswd() - - if err := scan.Prepare(); err != nil { - util.Log.Errorf("Failed to prepare: %s", err) - return subcommands.ExitFailure - } - - return subcommands.ExitSuccess -} diff --git a/commands/scan.go b/commands/scan.go index c65affdd..ebdef500 100644 --- a/commands/scan.go +++ b/commands/scan.go @@ -216,9 +216,6 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) return subcommands.ExitFailure } - util.Log.Info("Checking sudo configuration... ") - scan.CheckIfSudoNoPasswd() - util.Log.Info("Detecting Platforms... ") scan.DetectPlatforms() diff --git a/main.go b/main.go index d690ff8a..39520b39 100644 --- a/main.go +++ b/main.go @@ -41,7 +41,6 @@ func main() { subcommands.Register(&commands.DiscoverCmd{}, "discover") subcommands.Register(&commands.TuiCmd{}, "tui") subcommands.Register(&commands.ScanCmd{}, "scan") - subcommands.Register(&commands.PrepareCmd{}, "prepare") subcommands.Register(&commands.HistoryCmd{}, "history") subcommands.Register(&commands.ReportCmd{}, "report") subcommands.Register(&commands.ConfigtestCmd{}, "configtest") diff --git a/scan/base.go b/scan/base.go index 0fd3fe15..bd847331 100644 --- a/scan/base.go +++ b/scan/base.go @@ -34,7 +34,6 @@ type base struct { Distro config.Distro Platform models.Platform - lackDependencies []string osPackages log *logrus.Entry @@ -77,10 +76,6 @@ func (l base) getPlatform() models.Platform { return l.Platform } -func (l base) getLackDependencies() []string { - return l.lackDependencies -} - func (l base) allContainers() (containers []config.Container, err error) { switch l.ServerInfo.Container.Type { case "", "docker": diff --git a/scan/debian.go b/scan/debian.go index 70658e5c..80b33b23 100644 --- a/scan/debian.go +++ b/scan/debian.go @@ -128,12 +128,14 @@ func trim(str string) string { } func (o *debian) checkIfSudoNoPasswd() error { - r := o.exec("apt-get -v", noSudo) + cmd := util.PrependProxyEnv("apt-get update") + o.log.Infof("Checking... sudo %s", cmd) + r := o.exec(cmd, sudo) if !r.isSuccess() { o.log.Errorf("sudo error on %s", r) return fmt.Errorf("Failed to sudo: %s", r) } - o.log.Infof("sudo ... OK") + o.log.Infof("Sudo... Pass") return nil } @@ -145,11 +147,12 @@ func (o *debian) checkDependencies() error { case "debian": // Debian needs aptitude to get changelogs. // Because unable to get changelogs via apt-get changelog on Debian. - name := "aptitude" - cmd := name + " -h" - if r := o.exec(cmd, noSudo); !r.isSuccess() { - o.lackDependencies = []string{name} + if r := o.exec("test -f /usr/bin/aptitude", noSudo); !r.isSuccess() { + msg := fmt.Sprintf("aptitude is not installed: %s", r) + o.log.Errorf(msg) + return fmt.Errorf(msg) } + o.log.Infof("Dependencies... Pass") return nil default: @@ -157,32 +160,6 @@ func (o *debian) checkDependencies() error { } } -func (o *debian) install() error { - if len(o.lackDependencies) == 0 { - return nil - } - - // apt-get update - o.log.Infof("apt-get update...") - cmd := util.PrependProxyEnv("apt-get update") - if r := o.exec(cmd, sudo); !r.isSuccess() { - msg := fmt.Sprintf("Failed to SSH: %s", r) - o.log.Errorf(msg) - return fmt.Errorf(msg) - } - - for _, name := range o.lackDependencies { - cmd = util.PrependProxyEnv("apt-get install -y " + name) - if r := o.exec(cmd, sudo); !r.isSuccess() { - msg := fmt.Sprintf("Failed to SSH: %s", r) - o.log.Errorf(msg) - return fmt.Errorf(msg) - } - o.log.Infof("Installed: " + name) - } - return nil -} - func (o *debian) scanPackages() error { var err error var packs []models.PackageInfo @@ -244,17 +221,6 @@ func (o *debian) parseScannedPackagesLine(line string) (name, version string, er return "", "", fmt.Errorf("Unknown format: %s", line) } -func (o *debian) checkRequiredPackagesInstalled() error { - if o.Distro.Family == "debian" { - if r := o.exec("test -f /usr/bin/aptitude", noSudo); !r.isSuccess() { - msg := fmt.Sprintf("aptitude is not installed: %s", r) - o.log.Errorf(msg) - return fmt.Errorf(msg) - } - } - return nil -} - func (o *debian) scanUnsecurePackages(installed []models.PackageInfo) ([]models.VulnInfo, error) { o.log.Infof("apt-get update...") cmd := util.PrependProxyEnv("apt-get update") diff --git a/scan/executil.go b/scan/executil.go index 7d9cd40b..07ee5729 100644 --- a/scan/executil.go +++ b/scan/executil.go @@ -327,9 +327,6 @@ func decorateCmd(c conf.ServerInfo, cmd string, sudo bool) string { if sudo && c.User != "root" && !c.IsContainer() { cmd = fmt.Sprintf("sudo -S %s", cmd) cmd = strings.Replace(cmd, "|", "| sudo ", -1) - - // echo command does not need sudo (for CentOS) - cmd = strings.Replace(cmd, "sudo -S echo", "echo", -1) } // If you are using pipe and you want to detect preprocessing errors, remove comment out diff --git a/scan/freebsd.go b/scan/freebsd.go index 7aaf1790..f8712ef6 100644 --- a/scan/freebsd.go +++ b/scan/freebsd.go @@ -69,14 +69,6 @@ func (o *bsd) checkDependencies() error { return nil } -func (o *bsd) install() error { - return nil -} - -func (o *bsd) checkRequiredPackagesInstalled() error { - return nil -} - func (o *bsd) scanPackages() error { var err error var packs []models.PackageInfo diff --git a/scan/redhat.go b/scan/redhat.go index 06e74a56..c7bb44e7 100644 --- a/scan/redhat.go +++ b/scan/redhat.go @@ -102,83 +102,97 @@ func (o *redhat) checkIfSudoNoPasswd() error { return nil } - cmd := "yum --version" - if o.Distro.Family == "centos" { - cmd = "echo N | " + cmd + type cmd struct { + cmd string + expectedStatusCodes []int } - r := o.exec(cmd, o.sudo()) - if !r.isSuccess() { - o.log.Errorf("sudo error on %s", r) - return fmt.Errorf("Failed to sudo: %s", r) - } - o.log.Infof("sudo ... OK") - return nil -} + var cmds []cmd + var zero = []int{0} -// CentOS 5 ... yum-changelog -// CentOS 6 ... yum-plugin-changelog -// CentOS 7 ... yum-plugin-changelog -// RHEL, Amazon ... no additinal packages needed -func (o *redhat) checkDependencies() error { switch o.Distro.Family { - case "rhel", "amazon": - return nil - case "centos": + cmds = []cmd{ + {"yum --changelog --assumeno update yum", []int{0, 1}}, + } + + case "rhel": majorVersion, err := o.Distro.MajorVersion() if err != nil { return fmt.Errorf("Not implemented yet: %s, err: %s", o.Distro, err) } - var name = "yum-plugin-changelog" if majorVersion < 6 { - name = "yum-changelog" + cmds = []cmd{ + {"yum --color=never repolist", zero}, + {"yum --color=never check-update", []int{0, 100}}, + {"yum --color=never list-security --security", zero}, + {"yum --color=never info-security", zero}, + } + } else { + cmds = []cmd{ + {"yum --color=never repolist", zero}, + {"yum --color=never check-update", []int{0, 100}}, + {"yum --color=never --security updateinfo list updates", zero}, + {"yum --color=never --security updateinfo updates", zero}, + } } - - cmd := "rpm -q " + name - if r := o.exec(cmd, noSudo); r.isSuccess() { - return nil - } - o.lackDependencies = []string{name} - return nil - - default: - return fmt.Errorf("Not implemented yet: %s", o.Distro) } -} -func (o *redhat) install() error { - for _, name := range o.lackDependencies { - cmd := util.PrependProxyEnv("yum install -y " + name) - if r := o.exec(cmd, sudo); !r.isSuccess() { - return fmt.Errorf("Failed to SSH: %s", r) + for _, c := range cmds { + cmd := util.PrependProxyEnv(c.cmd) + o.log.Infof("Checking... sudo %s", cmd) + r := o.exec(util.PrependProxyEnv(cmd), o.sudo()) + if !r.isSuccess(c.expectedStatusCodes...) { + o.log.Errorf("Check sudo or proxy settings: %s", r) + return fmt.Errorf("Failed to sudo: %s", r) } - o.log.Infof("Installed: %s", name) } + o.log.Infof("Sudo... Pass") return nil } -func (o *redhat) checkRequiredPackagesInstalled() error { - if o.Distro.Family == "centos" { - majorVersion, err := o.Distro.MajorVersion() - if err != nil { - msg := fmt.Sprintf("Not implemented yet: %s, err: %s", o.Distro, err) - o.log.Errorf(msg) - return fmt.Errorf(msg) - } +// CentOS 5 ... yum-changelog +// CentOS 6, 7 ... yum-plugin-changelog +// RHEL 5 ... yum-security +// RHEL 6, 7 ... - +// Amazon ... - +func (o *redhat) checkDependencies() error { + var packName string + if o.Distro.Family == "amazon" { + return nil + } - var packName = "yum-plugin-changelog" + majorVersion, err := o.Distro.MajorVersion() + if err != nil { + msg := fmt.Sprintf("Not implemented yet: %s, err: %s", o.Distro, err) + o.log.Errorf(msg) + return fmt.Errorf(msg) + } + + switch o.Distro.Family { + case "centos": + packName = "yum-plugin-changelog" if majorVersion < 6 { packName = "yum-changelog" } - - cmd := "rpm -q " + packName - if r := o.exec(cmd, noSudo); !r.isSuccess() { - msg := fmt.Sprintf("%s is not installed", packName) - o.log.Errorf(msg) - return fmt.Errorf(msg) + case "rhel": + if majorVersion < 6 { + packName = "yum-security" + } else { + // yum-plugin-security is installed by default on RHEL6, 7 + return nil } + default: + return fmt.Errorf("Not implemented yet: %s", o.Distro) } + + cmd := "rpm -q " + packName + if r := o.exec(cmd, noSudo); !r.isSuccess() { + msg := fmt.Sprintf("%s is not installed", packName) + o.log.Errorf(msg) + return fmt.Errorf(msg) + } + o.log.Infof("Dependencies... Pass") return nil } @@ -551,7 +565,7 @@ func (o *redhat) getAllChangelog(packInfoList models.PackageInfoList) (stdout st packageNames += fmt.Sprintf("%s ", packInfo.Name) } - command := "echo N | " + command := "" if 0 < len(config.Conf.HTTPProxy) { command += util.ProxyEnv() } @@ -565,7 +579,7 @@ func (o *redhat) getAllChangelog(packInfoList models.PackageInfoList) (stdout st } // yum update --changelog doesn't have --color option. - command += fmt.Sprintf(" LANGUAGE=en_US.UTF-8 yum %s --changelog update ", yumopts) + packageNames + command += fmt.Sprintf(" LANGUAGE=en_US.UTF-8 yum --changelog --assumeno update %s ", yumopts) + packageNames r := o.exec(command, sudo) if !r.isSuccess(0, 1) { @@ -647,7 +661,7 @@ func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (models.VulnInfos, // get advisoryID(RHSA, ALAS) - CVE IDs if o.Distro.Family == "rhel" && major == 5 { - cmd = "yum --color=never info-sec" + cmd = "yum --color=never info-security" } else { cmd = "yum --color=never --security updateinfo updates" } diff --git a/scan/serverapi.go b/scan/serverapi.go index c35e76e9..c0198267 100644 --- a/scan/serverapi.go +++ b/scan/serverapi.go @@ -18,11 +18,9 @@ along with this program. If not, see . package scan import ( - "bufio" "fmt" "os" "path/filepath" - "strings" "time" "github.com/future-architect/vuls/cache" @@ -38,21 +36,16 @@ var servers, errServers []osTypeInterface type osTypeInterface interface { setServerInfo(config.ServerInfo) getServerInfo() config.ServerInfo - setDistro(string, string) getDistro() config.Distro - - // checkDependencies checks if dependencies are installed on the target server. - checkDependencies() error - getLackDependencies() []string - - checkIfSudoNoPasswd() error detectPlatform() getPlatform() models.Platform - checkRequiredPackagesInstalled() error + // checkDependencies checks if dependencies are installed on the target server. + checkDependencies() error + checkIfSudoNoPasswd() error + scanPackages() error - install() error convertToModel() models.ScanResult runningContainers() ([]config.Container, error) @@ -113,7 +106,7 @@ func detectOS(c config.ServerInfo) (osType osTypeInterface) { // PrintSSHableServerNames print SSH-able servernames func PrintSSHableServerNames() { - util.Log.Info("SSH-able servers are below...") + util.Log.Info("Scannable servers are below...") for _, s := range servers { if s.getServerInfo().IsContainer() { fmt.Printf("%s@%s ", @@ -338,9 +331,18 @@ func detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeIn return oses } +// CheckDependencies checks dependencies are installed on target servers. +func CheckDependencies() { + timeoutSec := 5 * 60 + parallelExec(func(o osTypeInterface) error { + return o.checkDependencies() + }, timeoutSec) + return +} + // CheckIfSudoNoPasswd checks whether vuls can sudo with nopassword via SSH func CheckIfSudoNoPasswd() { - timeoutSec := 15 + timeoutSec := 5 * 60 parallelExec(func(o osTypeInterface) error { return o.checkIfSudoNoPasswd() }, timeoutSec) @@ -380,117 +382,12 @@ func detectPlatforms() { return } -// Prepare installs requred packages to scan vulnerabilities. -func Prepare() error { - parallelExec(func(o osTypeInterface) error { - if err := o.checkDependencies(); err != nil { - return err - } - return nil - }) - - var targets, nonTargets []osTypeInterface - for _, s := range servers { - deps := s.getLackDependencies() - if len(deps) != 0 { - targets = append(targets, s) - } else { - nonTargets = append(nonTargets, s) - } - } - if len(targets) == 0 { - if 0 < len(nonTargets) { - util.Log.Info("The following servers were already installed dependencies") - for _, s := range nonTargets { - util.Log.Infof(" - %s", s.getServerInfo().GetServerName()) - } - } - - if 0 < len(errServers) { - util.Log.Error("Some errors occurred in the following servers") - for _, s := range errServers { - util.Log.Errorf(" - %s", s.getServerInfo().GetServerName()) - } - } else { - util.Log.Info("Success") - } - return nil - } - - util.Log.Info("The following servers need to install dependencies") - for _, s := range targets { - for _, d := range s.getLackDependencies() { - util.Log.Infof(" - %s on %s", d, s.getServerInfo().GetServerName()) - } - } - - if !config.Conf.AssumeYes { - util.Log.Info("Is this ok to install dependencies on the servers? [y/N]") - - reader := bufio.NewReader(os.Stdin) - for { - text, err := reader.ReadString('\n') - if err != nil { - return err - } - switch strings.TrimSpace(text) { - case "", "N", "n": - return nil - case "y", "Y": - goto yes - default: - util.Log.Info("Please enter y or N") - } - } - } - -yes: - servers = targets - parallelExec(func(o osTypeInterface) error { - if err := o.install(); err != nil { - return err - } - return nil - }) - - if 0 < len(servers) { - util.Log.Info("Successfully installed in the followring servers") - for _, s := range servers { - util.Log.Infof(" - %s", s.getServerInfo().GetServerName()) - } - } - - if 0 < len(nonTargets) { - util.Log.Info("The following servers were already installed dependencies") - for _, s := range nonTargets { - util.Log.Infof(" - %s", s.getServerInfo().GetServerName()) - } - } - - if 0 < len(errServers) { - util.Log.Error("Some errors occurred in the following servers") - for _, s := range errServers { - util.Log.Errorf(" - %s", s.getServerInfo().GetServerName()) - } - } - - if len(errServers) == 0 { - util.Log.Info("Success") - } else { - util.Log.Error("Failure") - } - return nil -} - // Scan scan func Scan() error { if len(servers) == 0 { return fmt.Errorf("No server defined. Check the configuration") } - util.Log.Info("Check required packages for scanning...") - checkRequiredPackagesInstalled() - if err := setupChangelogCache(); err != nil { return err } @@ -530,14 +427,6 @@ func setupChangelogCache() error { return nil } -func checkRequiredPackagesInstalled() []error { - timeoutSec := 30 * 60 - parallelExec(func(o osTypeInterface) error { - return o.checkRequiredPackagesInstalled() - }, timeoutSec) - return nil -} - func scanVulns(jsonDir string, scannedAt time.Time) error { var results models.ScanResults timeoutSec := 120 * 60 diff --git a/scan/unknownDistro.go b/scan/unknownDistro.go index b7221ba8..30b0ab76 100644 --- a/scan/unknownDistro.go +++ b/scan/unknownDistro.go @@ -30,14 +30,6 @@ func (o unknown) checkDependencies() error { return nil } -func (o *unknown) install() error { - return nil -} - -func (o *unknown) checkRequiredPackagesInstalled() error { - return nil -} - func (o *unknown) scanPackages() error { return nil } diff --git a/util/util.go b/util/util.go index 6942fb7c..3b94e8a6 100644 --- a/util/util.go +++ b/util/util.go @@ -95,7 +95,7 @@ func URLPathParamJoin(baseURL string, paths []string, params map[string]string) // ProxyEnv returns shell environment variables to set proxy func ProxyEnv() string { - httpProxyEnv := "env" + httpProxyEnv := "" keys := []string{ "http_proxy", "https_proxy", diff --git a/util/util_test.go b/util/util_test.go index 2ec538d4..49340a3d 100644 --- a/util/util_test.go +++ b/util/util_test.go @@ -105,14 +105,14 @@ func TestPrependHTTPProxyEnv(t *testing.T) { "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`, + ` 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" `, + ` 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{