Disable -ask-sudo-password for security reasons
This commit is contained in:
		
							
								
								
									
										37
									
								
								README.ja.md
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								README.ja.md
									
									
									
									
									
								
							@@ -112,6 +112,9 @@ $ cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
 | 
			
		||||
$ chmod 600 ~/.ssh/authorized_keys
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
VulsはSSHパスワード認証をサポートしていない。SSH公開鍵鍵認証を使う必要がある。
 | 
			
		||||
また、パスワードありのSUDOもセキュリティ上の理由によりサポートしていないため、スキャン対象サーバに/etc/sudoersにNOPASSWDを設定して、パスワードなしでSUDO可能にする必要がある。
 | 
			
		||||
 | 
			
		||||
## Step3. Install requirements
 | 
			
		||||
 | 
			
		||||
Vulsセットアップに必要な以下のソフトウェアをインストールする。
 | 
			
		||||
@@ -506,13 +509,13 @@ host         = "172.31.4.82"
 | 
			
		||||
    また、以下のSSH認証をサポートしている。
 | 
			
		||||
    - SSH agent
 | 
			
		||||
    - SSH public key authentication (with password, empty password)
 | 
			
		||||
    - Password authentication
 | 
			
		||||
    SSH Password認証はサポートしていない
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
# Usage: Configtest 
 | 
			
		||||
 | 
			
		||||
configtestサブコマンドは、config.tomlで定義されたサーバ/コンテナに対してSSH可能かどうかをチェックする。
 | 
			
		||||
configtestサブコマンドは、config.tomlで定義されたサーバ/コンテナに対してSSH可能かどうかをチェックする。  
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ vuls configtest --help
 | 
			
		||||
@@ -534,6 +537,18 @@ configtest:
 | 
			
		||||
        Use external ssh command. Default: Use the Go native implementation
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
また、スキャン対象サーバに対してパスワードなしでSUDO可能な状態かもチェックする。  
 | 
			
		||||
スキャン対象サーバ上の`/etc/sudoers`のサンプル
 | 
			
		||||
- CentOS, Amazon Linux, RedHat Enterprise Linux
 | 
			
		||||
```
 | 
			
		||||
vuls ALL=(root) NOPASSWD: /usr/bin/yum
 | 
			
		||||
```
 | 
			
		||||
- Ubuntu, Debian
 | 
			
		||||
```
 | 
			
		||||
vuls ALL=(root) NOPASSWD: /usr/bin/apt-get, /usr/bin/apt-cache
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
# Usage: Prepare
 | 
			
		||||
@@ -555,14 +570,11 @@ Prepareサブコマンドは、Vuls内部で利用する以下のパッケージ
 | 
			
		||||
$ vuls prepare -help
 | 
			
		||||
prepare
 | 
			
		||||
                        [-config=/path/to/config.toml] [-debug]
 | 
			
		||||
                        [-ask-sudo-password]
 | 
			
		||||
                        [-ask-key-password]
 | 
			
		||||
                        [SERVER]...
 | 
			
		||||
 | 
			
		||||
  -ask-key-password
 | 
			
		||||
        Ask ssh privatekey password before scanning
 | 
			
		||||
  -ask-sudo-password
 | 
			
		||||
        Ask sudo password of target servers before scanning
 | 
			
		||||
  -config string
 | 
			
		||||
        /path/to/toml (default "$PWD/config.toml")
 | 
			
		||||
  -debug
 | 
			
		||||
@@ -595,7 +607,6 @@ scan:
 | 
			
		||||
                [-report-slack]
 | 
			
		||||
                [-report-text]
 | 
			
		||||
                [-http-proxy=http://192.168.0.1:8080]
 | 
			
		||||
                [-ask-sudo-password]
 | 
			
		||||
                [-ask-key-password]
 | 
			
		||||
                [-debug]
 | 
			
		||||
                [-debug-sql]
 | 
			
		||||
@@ -611,8 +622,6 @@ scan:
 | 
			
		||||
 | 
			
		||||
  -ask-key-password
 | 
			
		||||
        Ask ssh privatekey password before scanning
 | 
			
		||||
  -ask-sudo-password
 | 
			
		||||
        Ask sudo password of target servers before scanning
 | 
			
		||||
  -aws-profile string
 | 
			
		||||
        AWS Profile to use (default "default")
 | 
			
		||||
  -aws-region string
 | 
			
		||||
@@ -685,14 +694,6 @@ Defaults:vuls !requiretty
 | 
			
		||||
| empty password   |                 -  | |
 | 
			
		||||
| with password    |           required | or use ssh-agent |
 | 
			
		||||
 | 
			
		||||
## -ask-sudo-password option
 | 
			
		||||
 | 
			
		||||
| sudo password on target servers | -ask-sudo-password | |
 | 
			
		||||
|:-----------------|:-------|:------|
 | 
			
		||||
| NOPASSWORD       | - | defined as NOPASSWORD in /etc/sudoers on target servers |
 | 
			
		||||
| with password    | required |  |
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## -report-json , -report-text option
 | 
			
		||||
 | 
			
		||||
結果をファイルに出力したい場合に指定する。出力先は、`$PWD/result/current/`    
 | 
			
		||||
@@ -705,12 +706,10 @@ $ vuls scan \
 | 
			
		||||
      -report-slack \ 
 | 
			
		||||
      -report-mail \
 | 
			
		||||
      -cvss-over=7 \
 | 
			
		||||
      -ask-sudo-password \ 
 | 
			
		||||
      -ask-key-password \
 | 
			
		||||
      -cve-dictionary-dbpath=$PWD/cve.sqlite3
 | 
			
		||||
```
 | 
			
		||||
この例では、
 | 
			
		||||
- スキャン対象サーバのsudoパスワードを指定
 | 
			
		||||
- SSH公開鍵認証(秘密鍵パスフレーズ)を指定
 | 
			
		||||
- configに定義された全サーバをスキャン
 | 
			
		||||
- レポートをslack, emailに送信
 | 
			
		||||
@@ -745,7 +744,6 @@ $ vuls scan \
 | 
			
		||||
```
 | 
			
		||||
この例では、
 | 
			
		||||
- SSH公開鍵認証(秘密鍵パスフレーズなし)
 | 
			
		||||
- ノーパスワードでsudoが実行可能
 | 
			
		||||
- configに定義された全サーバをスキャン
 | 
			
		||||
- 結果をJSON形式でS3に格納する。
 | 
			
		||||
  - バケット名 ... vuls
 | 
			
		||||
@@ -767,7 +765,6 @@ $ vuls scan \
 | 
			
		||||
```
 | 
			
		||||
この例では、
 | 
			
		||||
- SSH公開鍵認証(秘密鍵パスフレーズなし)
 | 
			
		||||
- ノーパスワードでsudoが実行可能
 | 
			
		||||
- configに定義された全サーバをスキャン
 | 
			
		||||
- 結果をJSON形式でAzure Blobに格納する。
 | 
			
		||||
  - コンテナ名 ... vuls
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										38
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								README.md
									
									
									
									
									
								
							@@ -120,6 +120,9 @@ $ cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
 | 
			
		||||
$ chmod 600 ~/.ssh/authorized_keys
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Vuls doesn't support SSH password authentication. So you have to use SSH key-based authentication.  
 | 
			
		||||
And also, SUDO with password is not supported for security reasons. So you have to define NOPASSWORD in /etc/sudoers on target servers.  
 | 
			
		||||
 | 
			
		||||
## Step3. Install requirements
 | 
			
		||||
 | 
			
		||||
Vuls requires the following packages.
 | 
			
		||||
@@ -506,15 +509,14 @@ You can customize your configuration using this template.
 | 
			
		||||
    
 | 
			
		||||
    Multiple SSH authentication methods are supported.  
 | 
			
		||||
    - SSH agent
 | 
			
		||||
    - SSH public key authentication (with password, empty password)
 | 
			
		||||
    - Password authentication
 | 
			
		||||
    - SSH public key authentication (with password and empty password)
 | 
			
		||||
    Password authentication is not supported.
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
# 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:
 | 
			
		||||
@@ -535,6 +537,16 @@ configtest:
 | 
			
		||||
        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.  
 | 
			
		||||
Example of /etc/sudoers on target servers
 | 
			
		||||
- CentOS, Amazon Linux, RedHat Enterprise Linux
 | 
			
		||||
```
 | 
			
		||||
vuls ALL=(root) NOPASSWD: /usr/bin/yum
 | 
			
		||||
```
 | 
			
		||||
- Ubuntu, Debian
 | 
			
		||||
```
 | 
			
		||||
vuls ALL=(root) NOPASSWD: /usr/bin/apt-get, /usr/bin/apt-cache
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
@@ -557,14 +569,11 @@ Prepare subcommand installs required packages on each server.
 | 
			
		||||
$ vuls prepare -help
 | 
			
		||||
prepare
 | 
			
		||||
                        [-config=/path/to/config.toml] [-debug]
 | 
			
		||||
                        [-ask-sudo-password]
 | 
			
		||||
                        [-ask-key-password]
 | 
			
		||||
                        [SERVER]...
 | 
			
		||||
 | 
			
		||||
  -ask-key-password
 | 
			
		||||
        Ask ssh privatekey password before scanning
 | 
			
		||||
  -ask-sudo-password
 | 
			
		||||
        Ask sudo password of target servers before scanning
 | 
			
		||||
  -config string
 | 
			
		||||
        /path/to/toml (default "$PWD/config.toml")
 | 
			
		||||
  -debug
 | 
			
		||||
@@ -597,7 +606,6 @@ scan:
 | 
			
		||||
                [-report-slack]
 | 
			
		||||
                [-report-text]
 | 
			
		||||
                [-http-proxy=http://192.168.0.1:8080]
 | 
			
		||||
                [-ask-sudo-password]
 | 
			
		||||
                [-ask-key-password]
 | 
			
		||||
                [-debug]
 | 
			
		||||
                [-debug-sql]
 | 
			
		||||
@@ -612,8 +620,6 @@ scan:
 | 
			
		||||
 | 
			
		||||
  -ask-key-password
 | 
			
		||||
        Ask ssh privatekey password before scanning
 | 
			
		||||
  -ask-sudo-password
 | 
			
		||||
        Ask sudo password of target servers before scanning
 | 
			
		||||
  -aws-profile string
 | 
			
		||||
        AWS Profile to use (default "default")
 | 
			
		||||
  -aws-region string
 | 
			
		||||
@@ -687,14 +693,6 @@ Defaults:vuls !requiretty
 | 
			
		||||
| empty password   |                 -  | |
 | 
			
		||||
| with password    |           required | or use ssh-agent |
 | 
			
		||||
 | 
			
		||||
## -ask-sudo-password option
 | 
			
		||||
 | 
			
		||||
| sudo password on target servers | -ask-sudo-password | |
 | 
			
		||||
|:-----------------|:-------|:------|
 | 
			
		||||
| NOPASSWORD       | - | defined as NOPASSWORD in /etc/sudoers on target servers |
 | 
			
		||||
| with password    | required |  |
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## -report-json , -report-text option
 | 
			
		||||
 | 
			
		||||
At the end of the scan, scan results will be available in the `$PWD/result/current/` directory.  
 | 
			
		||||
@@ -706,12 +704,11 @@ $ vuls scan \
 | 
			
		||||
      --report-slack \ 
 | 
			
		||||
      --report-mail \
 | 
			
		||||
      --cvss-over=7 \
 | 
			
		||||
      -ask-sudo-password \ 
 | 
			
		||||
      -ask-key-password \
 | 
			
		||||
      -cve-dictionary-dbpath=$PWD/cve.sqlite3
 | 
			
		||||
```
 | 
			
		||||
With this sample command, it will ..
 | 
			
		||||
- Ask sudo password and ssh key passsword before scanning
 | 
			
		||||
- Ask SSH key passsword before scanning
 | 
			
		||||
- Scan all servers defined in config file
 | 
			
		||||
- Send scan results to slack and email
 | 
			
		||||
- Only Report CVEs that CVSS score is over 7
 | 
			
		||||
@@ -725,7 +722,6 @@ $ vuls scan \
 | 
			
		||||
```
 | 
			
		||||
With this sample command, it will ..
 | 
			
		||||
- Use SSH Key-Based authentication with empty password (without -ask-key-password option)
 | 
			
		||||
- Sudo with no password (without -ask-sudo-password option)
 | 
			
		||||
- Scan only 2 servers (server1, server2)
 | 
			
		||||
- Print scan result to terminal
 | 
			
		||||
 | 
			
		||||
@@ -745,7 +741,6 @@ $ vuls scan \
 | 
			
		||||
```
 | 
			
		||||
With this sample command, it will ..
 | 
			
		||||
- Use SSH Key-Based authentication with empty password (without -ask-key-password option)
 | 
			
		||||
- Sudo with no password (without -ask-sudo-password option)
 | 
			
		||||
- Scan all servers defined in config file
 | 
			
		||||
- Put scan result(JSON) in S3 bucket. The bucket name is "vuls" in ap-northeast-1 and profile is "default"
 | 
			
		||||
 | 
			
		||||
@@ -764,7 +759,6 @@ $ vuls scan \
 | 
			
		||||
```
 | 
			
		||||
With this sample command, it will ..
 | 
			
		||||
- Use SSH Key-Based authentication with empty password (without -ask-key-password option)
 | 
			
		||||
- Sudo with no password (without -ask-sudo-password option)
 | 
			
		||||
- Scan all servers defined in config file
 | 
			
		||||
- Put scan result(JSON) in Azure Blob Storage. The container name is "vuls", storage account is "test" and accesskey is "access-key-string"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -98,7 +98,7 @@ func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interfa
 | 
			
		||||
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
 | 
			
		||||
	err = c.Load(p.configPath, keyPass, "")
 | 
			
		||||
	err = c.Load(p.configPath, keyPass)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
@@ -152,5 +152,11 @@ func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interfa
 | 
			
		||||
	Log.Info("Detecting Server/Contianer OS... ")
 | 
			
		||||
	scan.InitServers(Log)
 | 
			
		||||
 | 
			
		||||
	Log.Info("Checking sudo configuration... ")
 | 
			
		||||
	if err := scan.CheckIfSudoNoPasswd(Log); err != nil {
 | 
			
		||||
		Log.Errorf("Failed to sudo with nopassword via SSH. Define NOPASSWD in /etc/sudoers on target servers. err: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	scan.PrintSSHableServerNames()
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -61,7 +61,6 @@ func (*PrepareCmd) Usage() string {
 | 
			
		||||
	return `prepare:
 | 
			
		||||
	prepare
 | 
			
		||||
			[-config=/path/to/config.toml]
 | 
			
		||||
			[-ask-sudo-password]
 | 
			
		||||
			[-ask-key-password]
 | 
			
		||||
			[-debug]
 | 
			
		||||
 | 
			
		||||
@@ -90,7 +89,7 @@ func (p *PrepareCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
		&p.askSudoPassword,
 | 
			
		||||
		"ask-sudo-password",
 | 
			
		||||
		false,
 | 
			
		||||
		"Ask sudo password of target servers before scanning",
 | 
			
		||||
		"[Deprecated] THIS OPTION WAS REMOVED FOR SECURITY REASON. Define NOPASSWD in /etc/sudoers on tareget servers and use SSH key-based authentication",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
@@ -103,7 +102,7 @@ func (p *PrepareCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *PrepareCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	var keyPass, sudoPass string
 | 
			
		||||
	var keyPass string
 | 
			
		||||
	var err error
 | 
			
		||||
	if p.askKeyPassword {
 | 
			
		||||
		prompt := "SSH key password: "
 | 
			
		||||
@@ -113,14 +112,11 @@ func (p *PrepareCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if p.askSudoPassword {
 | 
			
		||||
		prompt := "sudo password: "
 | 
			
		||||
		if sudoPass, err = getPasswd(prompt); err != nil {
 | 
			
		||||
			logrus.Error(err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
		logrus.Errorf("[Deprecated] -ask-sudo-password WAS REMOVED FOR SECURITY REASONS. Define NOPASSWD in /etc/sudoers on tareget servers and use SSH key-based authentication")
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = c.Load(p.configPath, keyPass, sudoPass)
 | 
			
		||||
	err = c.Load(p.configPath, keyPass)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
 
 | 
			
		||||
@@ -102,7 +102,6 @@ func (*ScanCmd) Usage() string {
 | 
			
		||||
		[-report-slack]
 | 
			
		||||
		[-report-text]
 | 
			
		||||
		[-http-proxy=http://192.168.0.1:8080]
 | 
			
		||||
		[-ask-sudo-password]
 | 
			
		||||
		[-ask-key-password]
 | 
			
		||||
		[-debug]
 | 
			
		||||
		[-debug-sql]
 | 
			
		||||
@@ -211,7 +210,7 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
		&p.askSudoPassword,
 | 
			
		||||
		"ask-sudo-password",
 | 
			
		||||
		false,
 | 
			
		||||
		"Ask sudo password of target servers before scanning",
 | 
			
		||||
		"[Deprecated] THIS OPTION WAS REMOVED FOR SECURITY REASONS. Define NOPASSWD in /etc/sudoers on tareget servers and use SSH key-based authentication",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
@@ -232,7 +231,7 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	var keyPass, sudoPass string
 | 
			
		||||
	var keyPass string
 | 
			
		||||
	var err error
 | 
			
		||||
	if p.askKeyPassword {
 | 
			
		||||
		prompt := "SSH key password: "
 | 
			
		||||
@@ -242,14 +241,11 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if p.askSudoPassword {
 | 
			
		||||
		prompt := "sudo password: "
 | 
			
		||||
		if sudoPass, err = getPasswd(prompt); err != nil {
 | 
			
		||||
			logrus.Error(err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
		logrus.Errorf("[Deprecated] -ask-sudo-password WAS REMOVED FOR SECURITY REASONS. Define NOPASSWD in /etc/sudoers on tareget servers and use SSH key-based authentication")
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = c.Load(p.configPath, keyPass, sudoPass)
 | 
			
		||||
	err = c.Load(p.configPath, keyPass)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
@@ -383,6 +379,12 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
 | 
			
		||||
	Log.Info("Detecting Server/Contianer OS... ")
 | 
			
		||||
	scan.InitServers(Log)
 | 
			
		||||
 | 
			
		||||
	Log.Info("Checking sudo configuration... ")
 | 
			
		||||
	if err := scan.CheckIfSudoNoPasswd(Log); err != nil {
 | 
			
		||||
		Log.Errorf("Failed to sudo with nopassword via SSH. Define NOPASSWD in /etc/sudoers on target servers")
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Detecting Platforms... ")
 | 
			
		||||
	scan.DetectPlatforms(Log)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -216,7 +216,6 @@ func (c *SlackConf) Validate() (errs []error) {
 | 
			
		||||
type ServerInfo struct {
 | 
			
		||||
	ServerName  string
 | 
			
		||||
	User        string
 | 
			
		||||
	Password    string
 | 
			
		||||
	Host        string
 | 
			
		||||
	Port        string
 | 
			
		||||
	KeyPath     string
 | 
			
		||||
@@ -232,7 +231,6 @@ type ServerInfo struct {
 | 
			
		||||
 | 
			
		||||
	// used internal
 | 
			
		||||
	LogMsgAnsiColor string // DebugLog Color
 | 
			
		||||
	SudoOpt         SudoOption
 | 
			
		||||
	Container       Container
 | 
			
		||||
	Family          string
 | 
			
		||||
}
 | 
			
		||||
@@ -253,13 +251,3 @@ type Container struct {
 | 
			
		||||
	Name        string
 | 
			
		||||
	Type        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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,14 +18,13 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
// Load loads configuration
 | 
			
		||||
func Load(path, keyPass, sudoPass string) error {
 | 
			
		||||
func Load(path, keyPass string) error {
 | 
			
		||||
	var loader Loader
 | 
			
		||||
	loader = TOMLLoader{}
 | 
			
		||||
 | 
			
		||||
	return loader.Load(path, keyPass, sudoPass)
 | 
			
		||||
	return loader.Load(path, keyPass)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Loader is interface of concrete loader
 | 
			
		||||
type Loader interface {
 | 
			
		||||
	Load(string, string, string) error
 | 
			
		||||
	Load(string, string) error
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,7 @@ type TOMLLoader struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Load load the configuraiton TOML file specified by path arg.
 | 
			
		||||
func (c TOMLLoader) Load(pathToToml, keyPass, sudoPass string) (err error) {
 | 
			
		||||
func (c TOMLLoader) Load(pathToToml, keyPass string) (err error) {
 | 
			
		||||
	var conf Config
 | 
			
		||||
	if _, err := toml.DecodeFile(pathToToml, &conf); err != nil {
 | 
			
		||||
		log.Error("Load config failed", err)
 | 
			
		||||
@@ -49,15 +49,11 @@ func (c TOMLLoader) Load(pathToToml, keyPass, sudoPass string) (err error) {
 | 
			
		||||
		d.KeyPassword = keyPass
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if sudoPass != "" {
 | 
			
		||||
		d.Password = sudoPass
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	i := 0
 | 
			
		||||
	for name, v := range conf.Servers {
 | 
			
		||||
 | 
			
		||||
		if 0 < len(v.KeyPassword) || 0 < len(v.Password) {
 | 
			
		||||
			log.Warn("[Deprecated] password and keypassword in config file are unsecure. Remove them immediately for a security reason. They will be removed in a future release.")
 | 
			
		||||
		if 0 < len(v.KeyPassword) {
 | 
			
		||||
			log.Warn("[Deprecated] KEYPASSWORD IN CONFIG FILE ARE UNSECURE. REMOVE THEM IMMEDIATELY FOR A SECURITY REASONS. THEY WILL BE REMOVED IN A FUTURE RELEASE.")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s := ServerInfo{ServerName: name}
 | 
			
		||||
@@ -71,12 +67,6 @@ func (c TOMLLoader) Load(pathToToml, keyPass, sudoPass string) (err error) {
 | 
			
		||||
			return fmt.Errorf("%s is invalid. User is empty", name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//  s.Password = sudoPass
 | 
			
		||||
		s.Password = v.Password
 | 
			
		||||
		if s.Password == "" {
 | 
			
		||||
			s.Password = d.Password
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.Host = v.Host
 | 
			
		||||
		if s.Host == "" {
 | 
			
		||||
			return fmt.Errorf("%s is invalid. host is empty", name)
 | 
			
		||||
 
 | 
			
		||||
@@ -45,11 +45,7 @@ func newDebian(c config.ServerInfo) *debian {
 | 
			
		||||
// 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, err error) {
 | 
			
		||||
 | 
			
		||||
	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() {
 | 
			
		||||
@@ -119,6 +115,16 @@ func trim(str string) string {
 | 
			
		||||
	return strings.TrimSpace(str)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) checkIfSudoNoPasswd() error {
 | 
			
		||||
	r := o.ssh("apt-get -v", 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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) install() error {
 | 
			
		||||
 | 
			
		||||
	// apt-get update
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,12 @@ func detectFreebsd(c config.ServerInfo) (itsMe bool, bsd osTypeInterface) {
 | 
			
		||||
	return false, bsd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) checkIfSudoNoPasswd() error {
 | 
			
		||||
	// FreeBSD doesn't need root privilege
 | 
			
		||||
	o.log.Infof("sudo ... OK")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) install() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -47,11 +47,7 @@ func newRedhat(c config.ServerInfo) *redhat {
 | 
			
		||||
 | 
			
		||||
// 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{ExecBySudo: true}
 | 
			
		||||
	red.setServerInfo(c)
 | 
			
		||||
 | 
			
		||||
	if r := sshExec(c, "ls /etc/fedora-release", noSudo); r.isSuccess() {
 | 
			
		||||
@@ -102,6 +98,16 @@ func detectRedhat(c config.ServerInfo) (itsMe bool, red osTypeInterface) {
 | 
			
		||||
	return false, red
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) checkIfSudoNoPasswd() error {
 | 
			
		||||
	r := o.ssh("yum --version", 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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CentOS 5 ... yum-plugin-security, yum-changelog
 | 
			
		||||
// CentOS 6 ... yum-plugin-security, yum-plugin-changelog
 | 
			
		||||
// CentOS 7 ... yum-plugin-security, yum-plugin-changelog
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,7 @@ type osTypeInterface interface {
 | 
			
		||||
	setDistributionInfo(string, string)
 | 
			
		||||
	getDistributionInfo() string
 | 
			
		||||
 | 
			
		||||
	checkIfSudoNoPasswd() error
 | 
			
		||||
	detectPlatform() error
 | 
			
		||||
	getPlatform() models.Platform
 | 
			
		||||
 | 
			
		||||
@@ -133,14 +134,8 @@ func detectOS(c config.ServerInfo) (osType osTypeInterface) {
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// InitServers detect the kind of OS distribution of target servers
 | 
			
		||||
func InitServers(localLogger *logrus.Entry) {
 | 
			
		||||
	Log = localLogger
 | 
			
		||||
	servers = detectServerOSes()
 | 
			
		||||
 | 
			
		||||
	containers := detectContainerOSes()
 | 
			
		||||
	servers = append(servers, containers...)
 | 
			
		||||
 | 
			
		||||
// PrintSSHableServerNames print SSH-able servernames
 | 
			
		||||
func PrintSSHableServerNames() {
 | 
			
		||||
	Log.Info("SSH-able servers are below...")
 | 
			
		||||
	for _, s := range servers {
 | 
			
		||||
		if s.getServerInfo().IsContainer() {
 | 
			
		||||
@@ -155,6 +150,14 @@ func InitServers(localLogger *logrus.Entry) {
 | 
			
		||||
	fmt.Printf("\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// InitServers detect the kind of OS distribution of target servers
 | 
			
		||||
func InitServers(localLogger *logrus.Entry) {
 | 
			
		||||
	Log = localLogger
 | 
			
		||||
	servers = detectServerOSes()
 | 
			
		||||
	containers := detectContainerOSes()
 | 
			
		||||
	servers = append(servers, containers...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detectServerOSes() (sshAbleOses []osTypeInterface) {
 | 
			
		||||
	Log.Info("Detecting OS of servers... ")
 | 
			
		||||
	osTypeChan := make(chan osTypeInterface, len(config.Conf.Servers))
 | 
			
		||||
@@ -345,6 +348,19 @@ func detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeIn
 | 
			
		||||
	return oses
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIfSudoNoPasswd checks whether vuls can sudo with nopassword via SSH
 | 
			
		||||
func CheckIfSudoNoPasswd(localLogger *logrus.Entry) error {
 | 
			
		||||
	timeoutSec := 1 * 15
 | 
			
		||||
	errs := parallelSSHExec(func(o osTypeInterface) error {
 | 
			
		||||
		return o.checkIfSudoNoPasswd()
 | 
			
		||||
	}, timeoutSec)
 | 
			
		||||
 | 
			
		||||
	if 0 < len(errs) {
 | 
			
		||||
		return fmt.Errorf(fmt.Sprintf("%s", errs))
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DetectPlatforms detects the platform of each servers.
 | 
			
		||||
func DetectPlatforms(localLogger *logrus.Entry) {
 | 
			
		||||
	errs := detectPlatforms()
 | 
			
		||||
 
 | 
			
		||||
@@ -79,8 +79,11 @@ const sudo = true
 | 
			
		||||
const noSudo = false
 | 
			
		||||
 | 
			
		||||
func parallelSSHExec(fn func(osTypeInterface) error, timeoutSec ...int) (errs []error) {
 | 
			
		||||
	resChan := make(chan string, len(servers))
 | 
			
		||||
	errChan := make(chan error, len(servers))
 | 
			
		||||
	defer close(errChan)
 | 
			
		||||
	defer close(resChan)
 | 
			
		||||
 | 
			
		||||
	for _, s := range servers {
 | 
			
		||||
		go func(s osTypeInterface) {
 | 
			
		||||
			defer func() {
 | 
			
		||||
@@ -97,7 +100,7 @@ func parallelSSHExec(fn func(osTypeInterface) error, timeoutSec ...int) (errs []
 | 
			
		||||
					err,
 | 
			
		||||
				)
 | 
			
		||||
			} else {
 | 
			
		||||
				errChan <- nil
 | 
			
		||||
				resChan <- s.getServerInfo().ServerName
 | 
			
		||||
			}
 | 
			
		||||
		}(s)
 | 
			
		||||
	}
 | 
			
		||||
@@ -109,19 +112,40 @@ func parallelSSHExec(fn func(osTypeInterface) error, timeoutSec ...int) (errs []
 | 
			
		||||
		timeout = timeoutSec[0]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var snames []string
 | 
			
		||||
	isTimedout := false
 | 
			
		||||
	for i := 0; i < len(servers); i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case s := <-resChan:
 | 
			
		||||
			snames = append(snames, s)
 | 
			
		||||
		case err := <-errChan:
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				errs = append(errs, err)
 | 
			
		||||
			} else {
 | 
			
		||||
				logrus.Debug("Parallel SSH Success")
 | 
			
		||||
			}
 | 
			
		||||
			errs = append(errs, err)
 | 
			
		||||
		case <-time.After(time.Duration(timeout) * time.Second):
 | 
			
		||||
			logrus.Errorf("Parallel SSH Timeout")
 | 
			
		||||
			errs = append(errs, fmt.Errorf("Timed out"))
 | 
			
		||||
			isTimedout = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// collect timed out servernames
 | 
			
		||||
	var timedoutSnames []string
 | 
			
		||||
	if isTimedout {
 | 
			
		||||
		for _, s := range servers {
 | 
			
		||||
			name := s.getServerInfo().ServerName
 | 
			
		||||
			found := false
 | 
			
		||||
			for _, t := range snames {
 | 
			
		||||
				if name == t {
 | 
			
		||||
					found = true
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if !found {
 | 
			
		||||
				timedoutSnames = append(timedoutSnames, name)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if isTimedout {
 | 
			
		||||
		errs = append(errs, fmt.Errorf(
 | 
			
		||||
			"Timed out: %s", timedoutSnames))
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -196,7 +220,7 @@ func sshExecNative(c conf.ServerInfo, cmd string, sudo bool) (result sshResult)
 | 
			
		||||
 | 
			
		||||
	result.Stdout = stdoutBuf.String()
 | 
			
		||||
	result.Stderr = stderrBuf.String()
 | 
			
		||||
	result.Cmd = strings.Replace(maskPassword(cmd, c.Password), "\n", "", -1)
 | 
			
		||||
	result.Cmd = strings.Replace(cmd, "\n", "", -1)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -234,6 +258,8 @@ func sshExecExternal(c conf.ServerInfo, cmd string, sudo bool) (result sshResult
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd = decolateCmd(c, cmd, sudo)
 | 
			
		||||
	//  cmd = fmt.Sprintf("stty cols 256; set -o pipefail; %s", cmd)
 | 
			
		||||
 | 
			
		||||
	args = append(args, cmd)
 | 
			
		||||
	execCmd := exec.Command(sshBinaryPath, args...)
 | 
			
		||||
 | 
			
		||||
@@ -259,8 +285,7 @@ func sshExecExternal(c conf.ServerInfo, cmd string, sudo bool) (result sshResult
 | 
			
		||||
	result.Servername = c.ServerName
 | 
			
		||||
	result.Host = c.Host
 | 
			
		||||
	result.Port = c.Port
 | 
			
		||||
	result.Cmd = fmt.Sprintf("%s %s",
 | 
			
		||||
		sshBinaryPath, maskPassword(strings.Join(args, " "), c.Password))
 | 
			
		||||
	result.Cmd = fmt.Sprintf("%s %s", sshBinaryPath, strings.Join(args, " "))
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -272,14 +297,8 @@ func getSSHLogger(log ...*logrus.Entry) *logrus.Entry {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func decolateCmd(c conf.ServerInfo, cmd string, sudo bool) string {
 | 
			
		||||
	c.SudoOpt.ExecBySudo = true
 | 
			
		||||
	if sudo && c.User != "root" && !c.IsContainer() {
 | 
			
		||||
		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)
 | 
			
		||||
		}
 | 
			
		||||
		cmd = fmt.Sprintf("sudo -S %s", cmd)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Family != "FreeBSD" {
 | 
			
		||||
@@ -331,10 +350,6 @@ func sshConnect(c conf.ServerInfo) (client *ssh.Client, err error) {
 | 
			
		||||
		return nil, 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,
 | 
			
		||||
@@ -411,8 +426,3 @@ func parsePemBlock(block *pem.Block) (interface{}, error) {
 | 
			
		||||
		return nil, fmt.Errorf("Unsupported key type %q", block.Type)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ref golang.org/x/crypto/ssh/keys.go#ParseRawPrivateKey.
 | 
			
		||||
func maskPassword(cmd, sudoPass string) string {
 | 
			
		||||
	return strings.Replace(cmd, fmt.Sprintf("echo %s", sudoPass), "echo *****", -1)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user