Merge pull request #35 from future-architect/fix_to_no_password_in_config
Fix no password in config
This commit is contained in:
52
README.md
52
README.md
@@ -315,9 +315,7 @@ subjectPrefix = "[vuls]"
|
||||
[default]
|
||||
#port = "22"
|
||||
#user = "username"
|
||||
#password = "password"
|
||||
#keyPath = "/home/username/.ssh/id_rsa"
|
||||
#keyPassword = "password"
|
||||
|
||||
[servers]
|
||||
|
||||
@@ -325,9 +323,7 @@ subjectPrefix = "[vuls]"
|
||||
host = "172.31.4.82"
|
||||
#port = "22"
|
||||
#user = "root"
|
||||
#password = "password"
|
||||
#keyPath = "/home/username/.ssh/id_rsa"
|
||||
#keyPassword = "password"
|
||||
#cpeNames = [
|
||||
# "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
|
||||
#]
|
||||
@@ -395,9 +391,7 @@ You can customize your configuration using this template.
|
||||
[default]
|
||||
#port = "22"
|
||||
#user = "username"
|
||||
#password = "password"
|
||||
#keyPath = "/home/username/.ssh/id_rsa"
|
||||
#keyPassword = "password"
|
||||
```
|
||||
Items of the default section will be used if not specified.
|
||||
|
||||
@@ -409,9 +403,7 @@ You can customize your configuration using this template.
|
||||
host = "172.31.4.82"
|
||||
#port = "22"
|
||||
#user = "root"
|
||||
#password = "password"
|
||||
#keyPath = "/home/username/.ssh/id_rsa"
|
||||
#keyPassword = "password"
|
||||
#cpeNames = [
|
||||
# "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
|
||||
#]
|
||||
@@ -440,9 +432,15 @@ Prepare subcommand installs required packages on each server.
|
||||
|
||||
```
|
||||
$ vuls prepare -help
|
||||
prepare:
|
||||
prepare [-config=/path/to/config.toml] [-debug]
|
||||
prepare
|
||||
[-config=/path/to/config.toml] [-debug]
|
||||
[-ask-sudo-password]
|
||||
[-ask-key-password]
|
||||
|
||||
-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
|
||||
@@ -456,6 +454,7 @@ prepare:
|
||||
# Usage: Scan
|
||||
|
||||
```
|
||||
|
||||
$ vuls scan -help
|
||||
scan:
|
||||
scan
|
||||
@@ -467,8 +466,14 @@ scan:
|
||||
[-report-slack]
|
||||
[-report-mail]
|
||||
[-http-proxy=http://192.168.0.1:8080]
|
||||
[-ask-sudo-password]
|
||||
[-ask-key-password]
|
||||
[-debug]
|
||||
[-debug-sql]
|
||||
-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")
|
||||
-cve-dictionary-url string
|
||||
@@ -496,6 +501,21 @@ scan:
|
||||
|
||||
```
|
||||
|
||||
## ask-key-password option
|
||||
|
||||
| SSH key password | -ask-key-password ||
|
||||
|:-----------------|:-------------------||
|
||||
| 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 ||
|
||||
|
||||
|
||||
## example
|
||||
|
||||
Run go-cve-dictionary as server mode before scanning.
|
||||
@@ -505,9 +525,10 @@ $ go-cve-dictionary server
|
||||
|
||||
### Scan all servers defined in config file
|
||||
```
|
||||
$ vuls scan --report-slack --report-mail --cvss-over=7
|
||||
$ vuls scan --report-slack --report-mail --cvss-over=7 -ask-sudo-password -ask-key-password
|
||||
```
|
||||
With this sample command, it will ..
|
||||
- Ask sudo password and 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
|
||||
@@ -518,7 +539,9 @@ With this sample command, it will ..
|
||||
$ vuls scan server1 server2
|
||||
```
|
||||
With this sample command, it will ..
|
||||
- Scan only 2 servers. (server1, server2)
|
||||
- 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
|
||||
|
||||
----
|
||||
@@ -594,6 +617,11 @@ Use Systemd, Upstart or supervisord, daemontools...
|
||||
- How to Enable Automatic-Update of Vunerability Data.
|
||||
Use job scheduler like Cron (with -last2y option).
|
||||
|
||||
- How to Enable Automatic-Scan.
|
||||
Use job scheduler like Cron.
|
||||
Set NOPASSWORD option in /etc/sudoers on target servers.
|
||||
Use SSH Key-Based Authentication with empty password or ssh-agent.
|
||||
|
||||
- How to cross compile
|
||||
```bash
|
||||
$ cd /path/to/your/local-git-reporsitory/vuls
|
||||
|
||||
21
commands/cmdutil.go
Normal file
21
commands/cmdutil.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/howeyc/gopass"
|
||||
)
|
||||
|
||||
func getPasswd(prompt string) (string, error) {
|
||||
for {
|
||||
fmt.Print(prompt)
|
||||
pass, err := gopass.GetPasswdMasked()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to read password")
|
||||
}
|
||||
if 0 < len(pass) {
|
||||
return string(pass[:]), nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -111,9 +111,7 @@ subjectPrefix = "[vuls]"
|
||||
[default]
|
||||
#port = "22"
|
||||
#user = "username"
|
||||
#password = "password"
|
||||
#keyPath = "/home/username/.ssh/id_rsa"
|
||||
#keyPassword = "password"
|
||||
|
||||
[servers]
|
||||
{{- $names:= .Names}}
|
||||
@@ -122,9 +120,7 @@ subjectPrefix = "[vuls]"
|
||||
host = "{{$ip}}"
|
||||
#port = "22"
|
||||
#user = "root"
|
||||
#password = "password"
|
||||
#keyPath = "/home/username/.ssh/id_rsa"
|
||||
#keyPassword = "password"
|
||||
#cpeNames = [
|
||||
# "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
|
||||
#]
|
||||
|
||||
@@ -34,6 +34,9 @@ type PrepareCmd struct {
|
||||
debug bool
|
||||
configPath string
|
||||
|
||||
askSudoPassword bool
|
||||
askKeyPassword bool
|
||||
|
||||
useUnattendedUpgrades bool
|
||||
}
|
||||
|
||||
@@ -55,7 +58,11 @@ func (*PrepareCmd) Synopsis() string {
|
||||
// Usage return usage
|
||||
func (*PrepareCmd) Usage() string {
|
||||
return `prepare:
|
||||
prepare [-config=/path/to/config.toml] [-debug]
|
||||
prepare
|
||||
[-config=/path/to/config.toml]
|
||||
[-ask-sudo-password]
|
||||
[-ask-key-password]
|
||||
[-debug]
|
||||
|
||||
`
|
||||
}
|
||||
@@ -68,6 +75,20 @@ func (p *PrepareCmd) SetFlags(f *flag.FlagSet) {
|
||||
defaultConfPath := os.Getenv("PWD") + "/config.toml"
|
||||
f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
|
||||
|
||||
f.BoolVar(
|
||||
&p.askKeyPassword,
|
||||
"ask-key-password",
|
||||
false,
|
||||
"Ask ssh privatekey password before scanning",
|
||||
)
|
||||
|
||||
f.BoolVar(
|
||||
&p.askSudoPassword,
|
||||
"ask-sudo-password",
|
||||
false,
|
||||
"Ask sudo password of target servers before scanning",
|
||||
)
|
||||
|
||||
f.BoolVar(
|
||||
&p.useUnattendedUpgrades,
|
||||
"use-unattended-upgrades",
|
||||
@@ -78,14 +99,30 @@ func (p *PrepareCmd) SetFlags(f *flag.FlagSet) {
|
||||
|
||||
// Execute execute
|
||||
func (p *PrepareCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
logrus.Infof("Start Preparing (config: %s)", p.configPath)
|
||||
var keyPass, sudoPass string
|
||||
var err error
|
||||
if p.askKeyPassword {
|
||||
prompt := "SSH key password: "
|
||||
if keyPass, err = getPasswd(prompt); err != nil {
|
||||
logrus.Error(err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
}
|
||||
if p.askSudoPassword {
|
||||
prompt := "sudo password: "
|
||||
if sudoPass, err = getPasswd(prompt); err != nil {
|
||||
logrus.Error(err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
}
|
||||
|
||||
err := c.Load(p.configPath)
|
||||
err = c.Load(p.configPath, keyPass, sudoPass)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error loading %s, %s", p.configPath, err)
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
|
||||
logrus.Infof("Start Preparing (config: %s)", p.configPath)
|
||||
target := make(map[string]c.ServerInfo)
|
||||
for _, arg := range f.Args() {
|
||||
found := false
|
||||
|
||||
@@ -45,12 +45,15 @@ type ScanCmd struct {
|
||||
cvssScoreOver float64
|
||||
httpProxy string
|
||||
|
||||
useYumPluginSecurity bool
|
||||
useUnattendedUpgrades bool
|
||||
|
||||
// reporting
|
||||
reportSlack bool
|
||||
reportMail bool
|
||||
|
||||
askSudoPassword bool
|
||||
askKeyPassword bool
|
||||
|
||||
useYumPluginSecurity bool
|
||||
useUnattendedUpgrades bool
|
||||
}
|
||||
|
||||
// Name return subcommand name
|
||||
@@ -71,6 +74,8 @@ func (*ScanCmd) Usage() string {
|
||||
[-report-slack]
|
||||
[-report-mail]
|
||||
[-http-proxy=http://192.168.0.1:8080]
|
||||
[-ask-sudo-password]
|
||||
[-ask-key-password]
|
||||
[-debug]
|
||||
[-debug-sql]
|
||||
`
|
||||
@@ -111,6 +116,20 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
|
||||
f.BoolVar(&p.reportSlack, "report-slack", false, "Slack report")
|
||||
f.BoolVar(&p.reportMail, "report-mail", false, "Email report")
|
||||
|
||||
f.BoolVar(
|
||||
&p.askKeyPassword,
|
||||
"ask-key-password",
|
||||
false,
|
||||
"Ask ssh privatekey password before scanning",
|
||||
)
|
||||
|
||||
f.BoolVar(
|
||||
&p.askSudoPassword,
|
||||
"ask-sudo-password",
|
||||
false,
|
||||
"Ask sudo password of target servers before scanning",
|
||||
)
|
||||
|
||||
f.BoolVar(
|
||||
&p.useYumPluginSecurity,
|
||||
"use-yum-plugin-security",
|
||||
@@ -129,14 +148,30 @@ 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 err error
|
||||
if p.askKeyPassword {
|
||||
prompt := "SSH key password: "
|
||||
if keyPass, err = getPasswd(prompt); err != nil {
|
||||
logrus.Error(err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
}
|
||||
if p.askSudoPassword {
|
||||
prompt := "sudo password: "
|
||||
if sudoPass, err = getPasswd(prompt); err != nil {
|
||||
logrus.Error(err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
}
|
||||
|
||||
logrus.Infof("Start scanning (config: %s)", p.configPath)
|
||||
err := c.Load(p.configPath)
|
||||
err = c.Load(p.configPath, keyPass, sudoPass)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error loading %s, %s", p.configPath, err)
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
|
||||
logrus.Infof("Start scanning (config: %s)", p.configPath)
|
||||
target := make(map[string]c.ServerInfo)
|
||||
for _, arg := range f.Args() {
|
||||
found := false
|
||||
|
||||
@@ -24,6 +24,6 @@ type JSONLoader struct {
|
||||
}
|
||||
|
||||
// Load load the configuraiton JSON file specified by path arg.
|
||||
func (c JSONLoader) Load(path string) (err error) {
|
||||
func (c JSONLoader) Load(path, sudoPass, keyPass string) (err error) {
|
||||
return fmt.Errorf("Not implement yet")
|
||||
}
|
||||
|
||||
@@ -18,16 +18,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
package config
|
||||
|
||||
// Load loads configuration
|
||||
func Load(path string) error {
|
||||
|
||||
//TODO if path's suffix .toml
|
||||
func Load(path, keyPass, sudoPass string) error {
|
||||
var loader Loader
|
||||
loader = TOMLLoader{}
|
||||
|
||||
return loader.Load(path)
|
||||
return loader.Load(path, keyPass, sudoPass)
|
||||
}
|
||||
|
||||
// Loader is interface of concrete loader
|
||||
type Loader interface {
|
||||
Load(string) error
|
||||
Load(string, 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 string) (err error) {
|
||||
func (c TOMLLoader) Load(pathToToml, keyPass, sudoPass string) (err error) {
|
||||
var conf Config
|
||||
if _, err := toml.DecodeFile(pathToToml, &conf); err != nil {
|
||||
log.Error("Load config failed", err)
|
||||
@@ -45,14 +45,28 @@ func (c TOMLLoader) Load(pathToToml string) (err error) {
|
||||
Conf.Default = d
|
||||
servers := make(map[string]ServerInfo)
|
||||
|
||||
if keyPass != "" {
|
||||
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("[Depricated] password and keypassword in config file are unsecure. Remove them immediately for a security reason. They will be removed in a future release.")
|
||||
}
|
||||
|
||||
s := ServerInfo{ServerName: name}
|
||||
s.User = v.User
|
||||
if s.User == "" {
|
||||
s.User = d.User
|
||||
}
|
||||
|
||||
// s.Password = sudoPass
|
||||
s.Password = v.Password
|
||||
if s.Password == "" {
|
||||
s.Password = d.Password
|
||||
@@ -76,6 +90,7 @@ func (c TOMLLoader) Load(pathToToml string) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// s.KeyPassword = keyPass
|
||||
s.KeyPassword = v.KeyPassword
|
||||
if s.KeyPassword == "" {
|
||||
s.KeyPassword = d.KeyPassword
|
||||
|
||||
@@ -129,6 +129,7 @@ func (o *redhat) installYumPluginSecurity() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
o.log.Info("Installing yum-plugin-security...")
|
||||
cmd := util.PrependProxyEnv("yum install -y yum-plugin-security")
|
||||
if r := o.ssh(cmd, sudo); !r.isSuccess() {
|
||||
return fmt.Errorf(
|
||||
@@ -139,7 +140,6 @@ func (o *redhat) installYumPluginSecurity() error {
|
||||
}
|
||||
|
||||
func (o *redhat) installYumChangelog() error {
|
||||
o.log.Info("Installing yum-plugin-security...")
|
||||
|
||||
if o.Family == "centos" {
|
||||
var majorVersion int
|
||||
@@ -164,6 +164,7 @@ func (o *redhat) installYumChangelog() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
o.log.Infof("Installing %s...", packName)
|
||||
cmd = util.PrependProxyEnv("yum install -y " + packName)
|
||||
if r := o.ssh(cmd, sudo); !r.isSuccess() {
|
||||
return fmt.Errorf(
|
||||
@@ -458,7 +459,10 @@ func (o *redhat) parseYumCheckUpdateLine(line string) (models.PackageInfo, error
|
||||
}
|
||||
|
||||
func (o *redhat) getChangelog(packageNames string) (stdout string, err error) {
|
||||
command := "echo N | "
|
||||
command := ""
|
||||
if o.ServerInfo.User == "root" {
|
||||
command = "yes no | "
|
||||
}
|
||||
if 0 < len(config.Conf.HTTPProxy) {
|
||||
command += util.ProxyEnv()
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/k0kubun/pp"
|
||||
cve "github.com/kotakanbe/go-cve-dictionary/models"
|
||||
)
|
||||
|
||||
@@ -110,8 +109,6 @@ func InitServers(localLogger *logrus.Entry) (err error) {
|
||||
Log = localLogger
|
||||
if servers, err = detectServersOS(); err != nil {
|
||||
err = fmt.Errorf("Failed to detect the type of OS. err: %s", err)
|
||||
} else {
|
||||
Log.Debugf("%s", pp.Sprintf("%s", servers))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -137,7 +137,8 @@ func sshExec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (re
|
||||
// set pipefail option.
|
||||
// http://unix.stackexchange.com/questions/14270/get-exit-status-of-process-thats-piped-to-another
|
||||
cmd = fmt.Sprintf("set -o pipefail; %s", cmd)
|
||||
logger.Debugf("Command: %s", strings.Replace(cmd, "\n", "", -1))
|
||||
logger.Debugf("Command: %s",
|
||||
strings.Replace(maskPassword(cmd, c.Password), "\n", "", -1))
|
||||
|
||||
var client *ssh.Client
|
||||
client, err = sshConnect(c)
|
||||
@@ -189,7 +190,7 @@ func sshExec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (re
|
||||
|
||||
logger.Debugf(
|
||||
"SSH executed. cmd: %s, status: %#v\nstdout: \n%s\nstderr: \n%s",
|
||||
cmd, err, result.Stdout, result.Stderr)
|
||||
maskPassword(cmd, c.Password), err, result.Stdout, result.Stderr)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -225,7 +226,8 @@ func sshConnect(c conf.ServerInfo) (client *ssh.Client, err error) {
|
||||
|
||||
var auths = []ssh.AuthMethod{}
|
||||
if auths, err = addKeyAuth(auths, c.KeyPath, c.KeyPassword); err != nil {
|
||||
logrus.Fatalf("Failed to add keyAuth. err: %s", err)
|
||||
logrus.Fatalf("Failed to add keyAuth. %s@%s:%s err: %s",
|
||||
c.User, c.Host, c.Port, err)
|
||||
}
|
||||
|
||||
if c.Password != "" {
|
||||
@@ -237,12 +239,10 @@ func sshConnect(c conf.ServerInfo) (client *ssh.Client, err error) {
|
||||
User: c.User,
|
||||
Auth: auths,
|
||||
}
|
||||
// log.Debugf("config: %s", pp.Sprintf("%v", config))
|
||||
|
||||
notifyFunc := func(e error, t time.Duration) {
|
||||
logrus.Warnf("Failed to ssh %s@%s:%s. err: %s, Retrying in %s...",
|
||||
logrus.Warnf("Failed to ssh %s@%s:%s err: %s, Retrying in %s...",
|
||||
c.User, c.Host, c.Port, e, t)
|
||||
logrus.Debugf("sshConInfo: %s", pp.Sprintf("%v", c))
|
||||
}
|
||||
err = backoff.RetryNotify(func() error {
|
||||
if client, err = ssh.Dial("tcp", c.Host+":"+c.Port, config); err != nil {
|
||||
@@ -309,3 +309,8 @@ func parsePemBlock(block *pem.Block) (interface{}, error) {
|
||||
return nil, fmt.Errorf("rtop: 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