Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(configtest,scan): support SSH config file #1388

Merged
merged 2 commits into from
Feb 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions config/loader.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package config

// Load loads configuration
func Load(path, keyPass string) error {
var loader Loader
loader = TOMLLoader{}
return loader.Load(path, keyPass)
func Load(path string) error {
loader := TOMLLoader{}
return loader.Load(path)
}

// Loader is interface of concrete loader
Expand Down
11 changes: 2 additions & 9 deletions config/tomlloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type TOMLLoader struct {
}

// Load load the configuration TOML file specified by path arg.
func (c TOMLLoader) Load(pathToToml, _ string) error {
func (c TOMLLoader) Load(pathToToml string) error {
// util.Log.Infof("Loading config: %s", pathToToml)
if _, err := toml.DecodeFile(pathToToml, &Conf); err != nil {
return err
Expand Down Expand Up @@ -149,18 +149,11 @@ func setDefaultIfEmpty(server *ServerInfo) error {
}

if server.Port == "" {
if Conf.Default.Port != "" {
server.Port = Conf.Default.Port
} else {
server.Port = "22"
}
server.Port = Conf.Default.Port
}

if server.User == "" {
server.User = Conf.Default.User
if server.User == "" && server.Port != "local" {
return xerrors.Errorf("server.user is empty")
}
}

if server.SSHConfigPath == "" {
Expand Down
25 changes: 14 additions & 11 deletions scanner/executil.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,10 @@ func sshExecExternal(c config.ServerInfo, cmd string, sudo bool) (result execRes
return execResult{Error: err}
}

defaultSSHArgs := []string{"-tt"}
args := []string{"-tt"}

if 0 < len(c.SSHConfigPath) {
defaultSSHArgs = append(defaultSSHArgs, "-F", c.SSHConfigPath)
if c.SSHConfigPath != "" {
args = append(args, "-F", c.SSHConfigPath)
} else {
home, err := homedir.Dir()
if err != nil {
Expand All @@ -200,7 +200,7 @@ func sshExecExternal(c config.ServerInfo, cmd string, sudo bool) (result execRes
}
controlPath := filepath.Join(home, ".vuls", `controlmaster-%r-`+c.ServerName+`.%p`)

defaultSSHArgs = append(defaultSSHArgs,
args = append(args,
"-o", "StrictHostKeyChecking=yes",
"-o", "LogLevel=quiet",
"-o", "ConnectionAttempts=3",
Expand All @@ -212,19 +212,22 @@ func sshExecExternal(c config.ServerInfo, cmd string, sudo bool) (result execRes
}

if config.Conf.Vvv {
defaultSSHArgs = append(defaultSSHArgs, "-vvv")
args = append(args, "-vvv")
}

if len(c.JumpServer) != 0 {
defaultSSHArgs = append(defaultSSHArgs, "-J", strings.Join(c.JumpServer, ","))
args = append(args, "-J", strings.Join(c.JumpServer, ","))
}

args := append(defaultSSHArgs, fmt.Sprintf("%s@%s", c.User, c.Host))
args = append(args, "-p", c.Port)
if 0 < len(c.KeyPath) {
if c.User != "" {
args = append(args, "-l", c.User)
}
if c.Port != "" {
args = append(args, "-p", c.Port)
}
if c.KeyPath != "" {
args = append(args, "-i", c.KeyPath)
args = append(args, "-o", "PasswordAuthentication=no")
}
args = append(args, c.Host)

cmd = decorateCmd(c, cmd, sudo)
cmd = fmt.Sprintf("stty cols 1000; %s", cmd)
Expand Down
90 changes: 72 additions & 18 deletions scanner/serverapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package scanner
import (
"fmt"
"math/rand"
"net"
"net/http"
"os"
ex "os/exec"
"strings"
"time"

Expand Down Expand Up @@ -287,7 +289,7 @@ func (s Scanner) detectServerOSes() (servers, errServers []osTypeInterface) {
logging.Log.Debugf("Panic: %s on %s", p, srv.ServerName)
}
}()
if err := checkHostinKnownHosts(srv); err != nil {
if err := validateSSHConfig(&srv); err != nil {
checkOS := unknown{base{ServerInfo: srv}}
checkOS.setErrs([]error{err})
osTypeChan <- &checkOS
Expand Down Expand Up @@ -332,16 +334,36 @@ func (s Scanner) detectServerOSes() (servers, errServers []osTypeInterface) {
return
}

func checkHostinKnownHosts(c config.ServerInfo) error {
func validateSSHConfig(c *config.ServerInfo) error {
if isLocalExec(c.Port, c.Host) {
return nil
}

args := []string{"-G"}
if len(c.SSHConfigPath) > 0 {
args = []string{"-G", "-F", c.SSHConfigPath}
logging.Log.Debugf("Validating SSH Settings for Server:%s ...", c.GetServerName())

sshBinaryPath, err := ex.LookPath("ssh")
if err != nil {
return xerrors.Errorf("Failed to lookup ssh binary path. err: %w", err)
}
sshKeygenBinaryPath, err := ex.LookPath("ssh-keygen")
if err != nil {
return xerrors.Errorf("Failed to lookup ssh-keygen binary path. err: %w", err)
}

sshConfigCmd := []string{sshBinaryPath, "-G"}
if c.SSHConfigPath != "" {
sshConfigCmd = append(sshConfigCmd, "-F", c.SSHConfigPath)
}
if c.Port != "" {
sshConfigCmd = append(sshConfigCmd, "-p", c.Port)
}
r := localExec(c, fmt.Sprintf("ssh %s %s", strings.Join(args, " "), c.Host), noSudo)
if c.User != "" {
sshConfigCmd = append(sshConfigCmd, "-l", c.User)
}
sshConfigCmd = append(sshConfigCmd, c.Host)
cmd := strings.Join(sshConfigCmd, " ")
logging.Log.Debugf("Executing... %s", strings.Replace(cmd, "\n", "", -1))
r := localExec(*c, cmd, noSudo)
if !r.isSuccess() {
return xerrors.Errorf("Failed to print SSH configuration. err: %w", r.Error)
}
Expand All @@ -352,14 +374,31 @@ func checkHostinKnownHosts(c config.ServerInfo) error {
userKnownHosts string
)
for _, line := range strings.Split(r.Stdout, "\n") {
if strings.HasPrefix(line, "hostname ") {
if strings.HasPrefix(line, "user ") {
user := strings.TrimPrefix(line, "user ")
logging.Log.Debugf("Setting SSH User:%s for Server:%s ...", user, c.GetServerName())
c.User = user
} else if strings.HasPrefix(line, "hostname ") {
hostname = strings.TrimPrefix(line, "hostname ")
logging.Log.Debugf("Validating SSH HostName:%s for Server:%s ...", hostname, c.GetServerName())
if _, err := net.LookupHost(hostname); err != nil {
return xerrors.New("Failed to name resolution. Please check the HostName settings for SSH")
}
} else if strings.HasPrefix(line, "port ") {
port := strings.TrimPrefix(line, "port ")
logging.Log.Debugf("Setting SSH Port:%s for Server:%s ...", port, c.GetServerName())
c.Port = port
} else if strings.HasPrefix(line, "globalknownhostsfile ") {
globalKnownHosts = strings.TrimPrefix(line, "globalknownhostsfile ")
} else if strings.HasPrefix(line, "userknownhostsfile ") {
userKnownHosts = strings.TrimPrefix(line, "userknownhostsfile ")
}
}
if c.User == "" || c.Port == "" {
return xerrors.New("Failed to find User or Port setting. Please check the User or Port settings for SSH")
}

logging.Log.Debugf("Checking if the host's public key is in known_hosts...")

knownHostsPaths := []string{}
for _, knownHosts := range []string{userKnownHosts, globalKnownHosts} {
Expand All @@ -370,29 +409,44 @@ func checkHostinKnownHosts(c config.ServerInfo) error {
}
}
if len(knownHostsPaths) == 0 {
return xerrors.New("Failed to find any known_hosts to use. Please check the UserKnownHostsFile and GlobalKnownHostsFile settings for SSH.")
return xerrors.New("Failed to find any known_hosts to use. Please check the UserKnownHostsFile and GlobalKnownHostsFile settings for SSH")
}

for _, knownHosts := range knownHostsPaths {
if c.Port != "" && c.Port != "22" {
cmd := fmt.Sprintf("ssh-keygen -F %s -f %s", fmt.Sprintf("\"[%s]:%s\"", c.Host, c.Port), knownHosts)
if r := localExec(c, cmd, noSudo); r.isSuccess() {
cmd := fmt.Sprintf("%s -F %s -f %s", sshKeygenBinaryPath, fmt.Sprintf("\"[%s]:%s\"", hostname, c.Port), knownHosts)
logging.Log.Debugf("Executing... %s", strings.Replace(cmd, "\n", "", -1))
if r := localExec(*c, cmd, noSudo); r.isSuccess() {
return nil
}
}
cmd := fmt.Sprintf("ssh-keygen -F %s -f %s", c.Host, knownHosts)
if r := localExec(c, cmd, noSudo); r.isSuccess() {
cmd := fmt.Sprintf("%s -F %s -f %s", sshKeygenBinaryPath, hostname, knownHosts)
logging.Log.Debugf("Executing... %s", strings.Replace(cmd, "\n", "", -1))
if r := localExec(*c, cmd, noSudo); r.isSuccess() {
return nil
}
}

sshCmd := fmt.Sprintf("ssh -i %s %s@%s", c.KeyPath, c.User, c.Host)
sshKeyScancmd := fmt.Sprintf("ssh-keyscan -H %s >> %s", hostname, knownHostsPaths[0])
if c.Port != "" && c.Port != "22" {
sshCmd = fmt.Sprintf("ssh -i %s -p %s %s@%s", c.KeyPath, c.Port, c.User, c.Host)
sshKeyScancmd = fmt.Sprintf("ssh-keyscan -H -p %s %s >> %s", c.Port, hostname, knownHostsPaths[0])
sshConnArgs := []string{}
sshKeyScanArgs := []string{"-H"}
if c.SSHConfigPath != "" {
sshConnArgs = append(sshConnArgs, "-F", c.SSHConfigPath)
}
if c.KeyPath != "" {
sshConnArgs = append(sshConnArgs, "-i", c.KeyPath)
}
if c.Port != "" {
sshConnArgs = append(sshConnArgs, "-p", c.Port)
sshKeyScanArgs = append(sshKeyScanArgs, "-p", c.Port)
}
if c.User != "" {
sshConnArgs = append(sshConnArgs, "-l", c.User)
}
return xerrors.Errorf("Failed to find the host in known_hosts. Plaese exec `$ %s` or `$ %s`", sshCmd, sshKeyScancmd)
sshConnArgs = append(sshConnArgs, c.Host)
sshKeyScanArgs = append(sshKeyScanArgs, fmt.Sprintf("%s >> %s", hostname, knownHostsPaths[0]))
sshConnCmd := fmt.Sprintf("ssh %s", strings.Join(sshConnArgs, " "))
sshKeyScancmd := fmt.Sprintf("ssh-keyscan %s", strings.Join(sshKeyScanArgs, " "))
return xerrors.Errorf("Failed to find the host in known_hosts. Plaese exec `$ %s` or `$ %s`", sshConnCmd, sshKeyScancmd)
}

func (s Scanner) detectContainerOSes(hosts []osTypeInterface) (actives, inactives []osTypeInterface) {
Expand Down
23 changes: 3 additions & 20 deletions subcmds/configtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@ import (

// ConfigtestCmd is Subcommand
type ConfigtestCmd struct {
configPath string
askKeyPassword bool
timeoutSec int
configPath string
timeoutSec int
}

// Name return subcommand name
Expand All @@ -35,7 +34,6 @@ func (*ConfigtestCmd) Usage() string {
[-config=/path/to/config.toml]
[-log-to-file]
[-log-dir=/path/to/log]
[-ask-key-password]
[-timeout=300]
[-containers-only]
[-http-proxy=http://192.168.0.1:8080]
Expand All @@ -59,10 +57,6 @@ func (p *ConfigtestCmd) SetFlags(f *flag.FlagSet) {

f.IntVar(&p.timeoutSec, "timeout", 5*60, "Timeout(Sec)")

f.BoolVar(&p.askKeyPassword, "ask-key-password", false,
"Ask ssh privatekey password before scanning",
)

f.StringVar(&config.Conf.HTTPProxy, "http-proxy", "",
"http://proxy-url:port (default: empty)")

Expand All @@ -79,18 +73,7 @@ func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interfa
return subcommands.ExitUsageError
}

var keyPass string
var err error
if p.askKeyPassword {
prompt := "SSH key password: "
if keyPass, err = getPasswd(prompt); err != nil {
logging.Log.Error(err)
return subcommands.ExitFailure
}
}

err = config.Load(p.configPath, keyPass)
if err != nil {
if err := config.Load(p.configPath); err != nil {
msg := []string{
fmt.Sprintf("Error loading %s", p.configPath),
"If you update Vuls and get this error, there may be incompatible changes in config.toml",
Expand Down
2 changes: 1 addition & 1 deletion subcmds/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}
logging.Log = logging.NewCustomLogger(config.Conf.Debug, config.Conf.Quiet, config.Conf.LogToFile, config.Conf.LogDir, "", "")
logging.Log.Infof("vuls-%s-%s", config.Version, config.Revision)

if err := config.Load(p.configPath, ""); err != nil {
if err := config.Load(p.configPath); err != nil {
logging.Log.Errorf("Error loading %s, %+v", p.configPath, err)
return subcommands.ExitUsageError
}
Expand Down
2 changes: 1 addition & 1 deletion subcmds/saas.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func (p *SaaSCmd) SetFlags(f *flag.FlagSet) {
func (p *SaaSCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
logging.Log = logging.NewCustomLogger(config.Conf.Debug, config.Conf.Quiet, config.Conf.LogToFile, config.Conf.LogDir, "", "")
logging.Log.Infof("vuls-%s-%s", config.Version, config.Revision)
if err := config.Load(p.configPath, ""); err != nil {
if err := config.Load(p.configPath); err != nil {
logging.Log.Errorf("Error loading %s, %+v", p.configPath, err)
return subcommands.ExitUsageError
}
Expand Down
19 changes: 1 addition & 18 deletions subcmds/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
// ScanCmd is Subcommand of host discovery mode
type ScanCmd struct {
configPath string
askKeyPassword bool
timeoutSec int
scanTimeoutSec int
cacheDBPath string
Expand All @@ -43,7 +42,6 @@ func (*ScanCmd) Usage() string {
[-log-dir=/path/to/log]
[-cachedb-path=/path/to/cache.db]
[-http-proxy=http://192.168.0.1:8080]
[-ask-key-password]
[-timeout=300]
[-timeout-scan=7200]
[-debug]
Expand Down Expand Up @@ -80,10 +78,6 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
f.StringVar(&config.Conf.HTTPProxy, "http-proxy", "",
"http://proxy-url:port (default: empty)")

f.BoolVar(&p.askKeyPassword, "ask-key-password", false,
"Ask ssh privatekey password before scanning",
)

f.BoolVar(&config.Conf.Pipe, "pipe", false, "Use stdin via PIPE")

f.BoolVar(&p.detectIPS, "ips", false, "retrieve IPS information")
Expand Down Expand Up @@ -116,18 +110,7 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
}
}

var keyPass string
var err error
if p.askKeyPassword {
prompt := "SSH key password: "
if keyPass, err = getPasswd(prompt); err != nil {
logging.Log.Error(err)
return subcommands.ExitFailure
}
}

err = config.Load(p.configPath, keyPass)
if err != nil {
if err := config.Load(p.configPath); err != nil {
msg := []string{
fmt.Sprintf("Error loading %s", p.configPath),
"If you update Vuls and get this error, there may be incompatible changes in config.toml",
Expand Down
2 changes: 1 addition & 1 deletion subcmds/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (p *ServerCmd) SetFlags(f *flag.FlagSet) {
func (p *ServerCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
logging.Log = logging.NewCustomLogger(config.Conf.Debug, config.Conf.Quiet, config.Conf.LogToFile, config.Conf.LogDir, "", "")
logging.Log.Infof("vuls-%s-%s", config.Version, config.Revision)
if err := config.Load(p.configPath, ""); err != nil {
if err := config.Load(p.configPath); err != nil {
logging.Log.Errorf("Error loading %s. err: %+v", p.configPath, err)
return subcommands.ExitUsageError
}
Expand Down
2 changes: 1 addition & 1 deletion subcmds/tui.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func (p *TuiCmd) SetFlags(f *flag.FlagSet) {
func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
logging.Log = logging.NewCustomLogger(config.Conf.Debug, config.Conf.Quiet, config.Conf.LogToFile, config.Conf.LogDir, "", "")
logging.Log.Infof("vuls-%s-%s", config.Version, config.Revision)
if err := config.Load(p.configPath, ""); err != nil {
if err := config.Load(p.configPath); err != nil {
logging.Log.Errorf("Error loading %s, err: %+v", p.configPath, err)
return subcommands.ExitUsageError
}
Expand Down
Loading