Merge pull request #148 from future-architect/remove-ask-sudo-password
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