Support scanning with external ssh command
This commit is contained in:
@@ -35,6 +35,7 @@ func detectFreebsd(c config.ServerInfo) (itsMe bool, bsd osTypeInterface) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Log.Debugf("Not FreeBSD. Host: %s:%s", c.Host, c.Port)
|
||||
return false, bsd
|
||||
}
|
||||
|
||||
|
||||
@@ -108,19 +108,19 @@ func (s CvePacksList) Less(i, j int) bool {
|
||||
|
||||
func detectOS(c config.ServerInfo) (osType osTypeInterface) {
|
||||
var itsMe bool
|
||||
itsMe, osType = detectDebian(c)
|
||||
if itsMe {
|
||||
if itsMe, osType = detectDebian(c); itsMe {
|
||||
Log.Debugf("Debian like Linux. Host: %s:%s", c.Host, c.Port)
|
||||
return
|
||||
}
|
||||
itsMe, osType = detectRedhat(c)
|
||||
if itsMe {
|
||||
if itsMe, osType = detectRedhat(c); itsMe {
|
||||
Log.Debugf("Redhat like Linux. Host: %s:%s", c.Host, c.Port)
|
||||
return
|
||||
}
|
||||
itsMe, osType = detectFreebsd(c)
|
||||
if itsMe {
|
||||
if itsMe, osType = detectFreebsd(c); itsMe {
|
||||
Log.Debugf("FreeBSD. Host: %s:%s", c.Host, c.Port)
|
||||
return
|
||||
}
|
||||
|
||||
osType.setServerInfo(c)
|
||||
osType.setErrs([]error{fmt.Errorf("Unknown OS Type")})
|
||||
return
|
||||
}
|
||||
@@ -177,18 +177,21 @@ func detectServerOSes() (oses []osTypeInterface, err error) {
|
||||
}(s)
|
||||
}
|
||||
|
||||
timeout := time.After(300 * time.Second)
|
||||
timeout := time.After(60 * time.Second)
|
||||
for i := 0; i < len(config.Conf.Servers); i++ {
|
||||
select {
|
||||
case res := <-osTypeChan:
|
||||
if 0 < len(res.getErrs()) {
|
||||
continue
|
||||
}
|
||||
Log.Infof("(%d/%d) Detected %s: %s",
|
||||
i+1, len(config.Conf.Servers),
|
||||
res.getServerInfo().ServerName,
|
||||
res.getDistributionInfo())
|
||||
oses = append(oses, res)
|
||||
if 0 < len(res.getErrs()) {
|
||||
Log.Infof("(%d/%d) Failed %s",
|
||||
i+1, len(config.Conf.Servers),
|
||||
res.getServerInfo().ServerName)
|
||||
} else {
|
||||
Log.Infof("(%d/%d) Detected %s: %s",
|
||||
i+1, len(config.Conf.Servers),
|
||||
res.getServerInfo().ServerName,
|
||||
res.getDistributionInfo())
|
||||
}
|
||||
case <-timeout:
|
||||
msg := "Timeout occurred while detecting"
|
||||
Log.Error(msg)
|
||||
@@ -208,19 +211,16 @@ func detectServerOSes() (oses []osTypeInterface, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
errorOccurred := false
|
||||
errs := []error{}
|
||||
for _, osi := range oses {
|
||||
if errs := osi.getErrs(); 0 < len(errs) {
|
||||
errorOccurred = true
|
||||
Log.Errorf("Some errors occurred on %s",
|
||||
osi.getServerInfo().ServerName)
|
||||
for _, err := range errs {
|
||||
Log.Error(err)
|
||||
}
|
||||
if 0 < len(osi.getErrs()) {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"Error occurred on %s. errs: %s",
|
||||
osi.getServerInfo().ServerName, osi.getErrs()))
|
||||
}
|
||||
}
|
||||
if errorOccurred {
|
||||
return oses, fmt.Errorf("Some errors occurred")
|
||||
if 0 < len(errs) {
|
||||
return oses, fmt.Errorf("%s", errs)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -234,10 +234,11 @@ func detectContainerOSes() (oses []osTypeInterface, err error) {
|
||||
}(s)
|
||||
}
|
||||
|
||||
timeout := time.After(300 * time.Second)
|
||||
timeout := time.After(60 * time.Second)
|
||||
for i := 0; i < len(config.Conf.Servers); i++ {
|
||||
select {
|
||||
case res := <-osTypesChan:
|
||||
oses = append(oses, res...)
|
||||
for _, osi := range res {
|
||||
if 0 < len(osi.getErrs()) {
|
||||
continue
|
||||
@@ -247,7 +248,6 @@ func detectContainerOSes() (oses []osTypeInterface, err error) {
|
||||
sinfo.Container.ContainerID, sinfo.Container.Name,
|
||||
sinfo.ServerName, osi.getDistributionInfo())
|
||||
}
|
||||
oses = append(oses, res...)
|
||||
case <-timeout:
|
||||
msg := "Timeout occurred while detecting"
|
||||
Log.Error(msg)
|
||||
@@ -267,19 +267,16 @@ func detectContainerOSes() (oses []osTypeInterface, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
errorOccurred := false
|
||||
errs := []error{}
|
||||
for _, osi := range oses {
|
||||
if errs := osi.getErrs(); 0 < len(errs) {
|
||||
errorOccurred = true
|
||||
Log.Errorf("Some errors occurred on %s",
|
||||
osi.getServerInfo().ServerName)
|
||||
for _, err := range errs {
|
||||
Log.Error(err)
|
||||
}
|
||||
if 0 < len(osi.getErrs()) {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"Error occurred on %s. errs: %s",
|
||||
osi.getServerInfo().ServerName, osi.getErrs()))
|
||||
}
|
||||
}
|
||||
if errorOccurred {
|
||||
return oses, fmt.Errorf("Some errors occurred")
|
||||
if 0 < len(errs) {
|
||||
return oses, fmt.Errorf("%s", errs)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
170
scan/sshutil.go
170
scan/sshutil.go
@@ -25,7 +25,10 @@ import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
@@ -105,53 +108,21 @@ func parallelSSHExec(fn func(osTypeInterface) error, timeoutSec ...int) (errs []
|
||||
}
|
||||
|
||||
func sshExec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (result sshResult) {
|
||||
// Setup Logger
|
||||
var logger *logrus.Entry
|
||||
if len(log) == 0 {
|
||||
level := logrus.InfoLevel
|
||||
if conf.Conf.Debug == true {
|
||||
level = logrus.DebugLevel
|
||||
}
|
||||
l := &logrus.Logger{
|
||||
Out: os.Stderr,
|
||||
Formatter: new(logrus.TextFormatter),
|
||||
Hooks: make(logrus.LevelHooks),
|
||||
Level: level,
|
||||
}
|
||||
logger = logrus.NewEntry(l)
|
||||
} else {
|
||||
logger = log[0]
|
||||
}
|
||||
c.SudoOpt.ExecBySudo = true
|
||||
var err error
|
||||
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)
|
||||
default:
|
||||
logger.Panicf("sudoOpt is invalid. SudoOpt: %v", c.SudoOpt)
|
||||
}
|
||||
if runtime.GOOS == "windows" || !conf.Conf.SSHExternal {
|
||||
return sshExecNative(c, cmd, sudo, log...)
|
||||
}
|
||||
return sshExecExternal(c, cmd, sudo, log...)
|
||||
}
|
||||
|
||||
if c.Family != "FreeBSD" {
|
||||
// set pipefail option. Bash only
|
||||
// http://unix.stackexchange.com/questions/14270/get-exit-status-of-process-thats-piped-to-another
|
||||
cmd = fmt.Sprintf("set -o pipefail; %s", cmd)
|
||||
}
|
||||
|
||||
if c.IsContainer() {
|
||||
switch c.Container.Type {
|
||||
case "", "docker":
|
||||
cmd = fmt.Sprintf(`docker exec %s /bin/bash -c "%s"`, c.Container.ContainerID, cmd)
|
||||
}
|
||||
}
|
||||
func sshExecNative(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (result sshResult) {
|
||||
logger := getSSHLogger(log...)
|
||||
|
||||
cmd = decolateCmd(c, cmd, sudo)
|
||||
logger.Debugf("Command: %s",
|
||||
strings.Replace(maskPassword(cmd, c.Password), "\n", "", -1))
|
||||
|
||||
var client *ssh.Client
|
||||
var err error
|
||||
client, err = sshConnect(c)
|
||||
defer client.Close()
|
||||
|
||||
@@ -200,12 +171,125 @@ func sshExec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (re
|
||||
result.Port = c.Port
|
||||
|
||||
logger.Debugf(
|
||||
"SSH executed. cmd: %s, status: %#v\nstdout: \n%s\nstderr: \n%s",
|
||||
maskPassword(cmd, c.Password), err, result.Stdout, result.Stderr)
|
||||
"SSH executed. cmd: %s, err: %#v, status: %d\nstdout: \n%s\nstderr: \n%s",
|
||||
maskPassword(cmd, c.Password), err, result.ExitStatus, result.Stdout, result.Stderr)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func sshExecExternal(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (result sshResult) {
|
||||
logger := getSSHLogger(log...)
|
||||
|
||||
sshBinaryPath, err := exec.LookPath("ssh")
|
||||
if err != nil {
|
||||
logger.Debug("Failed to find SSH binary, using native Go implementation")
|
||||
return sshExecNative(c, cmd, sudo, log...)
|
||||
}
|
||||
|
||||
defaultSSHArgs := []string{
|
||||
"-o", "StrictHostKeyChecking=no",
|
||||
"-o", "UserKnownHostsFile=/dev/null",
|
||||
"-o", "LogLevel=quiet",
|
||||
"-o", "ConnectionAttempts=3",
|
||||
"-o", "ConnectTimeout=10",
|
||||
"-o", "ControlMaster=no",
|
||||
"-o", "ControlPath=none",
|
||||
|
||||
// TODO ssh session multiplexing
|
||||
// "-o", "ControlMaster=auto",
|
||||
// "-o", `ControlPath=~/.ssh/controlmaster-%r-%h.%p`,
|
||||
// "-o", "Controlpersist=30m",
|
||||
}
|
||||
args := append(defaultSSHArgs, fmt.Sprintf("%s@%s", c.User, c.Host))
|
||||
args = append(args, "-p", c.Port)
|
||||
|
||||
// if conf.Conf.Debug {
|
||||
// args = append(args, "-v")
|
||||
// }
|
||||
|
||||
if 0 < len(c.KeyPath) {
|
||||
args = append(args, "-i", c.KeyPath)
|
||||
args = append(args, "-o", "PasswordAuthentication=no")
|
||||
}
|
||||
|
||||
cmd = decolateCmd(c, cmd, sudo)
|
||||
args = append(args, cmd)
|
||||
execCmd := exec.Command(sshBinaryPath, args...)
|
||||
|
||||
var stdoutBuf, stderrBuf bytes.Buffer
|
||||
execCmd.Stdout = &stdoutBuf
|
||||
execCmd.Stderr = &stderrBuf
|
||||
if err := execCmd.Run(); err != nil {
|
||||
if e, ok := err.(*exec.ExitError); ok {
|
||||
if s, ok := e.Sys().(syscall.WaitStatus); ok {
|
||||
result.ExitStatus = s.ExitStatus()
|
||||
} else {
|
||||
result.ExitStatus = 998
|
||||
}
|
||||
} else {
|
||||
result.ExitStatus = 999
|
||||
}
|
||||
} else {
|
||||
result.ExitStatus = 0
|
||||
}
|
||||
|
||||
result.Stdout = stdoutBuf.String()
|
||||
result.Stderr = stderrBuf.String()
|
||||
result.Host = c.Host
|
||||
result.Port = c.Port
|
||||
|
||||
logger.Debugf(
|
||||
"SSH executed. cmd: %s %s, err: %#v, status: %d\nstdout: \n%s\nstderr: \n%s",
|
||||
sshBinaryPath,
|
||||
maskPassword(strings.Join(args, " "), c.Password),
|
||||
err, result.ExitStatus, result.Stdout, result.Stderr)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getSSHLogger(log ...*logrus.Entry) *logrus.Entry {
|
||||
if len(log) == 0 {
|
||||
level := logrus.InfoLevel
|
||||
if conf.Conf.Debug == true {
|
||||
level = logrus.DebugLevel
|
||||
}
|
||||
l := &logrus.Logger{
|
||||
Out: os.Stderr,
|
||||
Formatter: new(logrus.TextFormatter),
|
||||
Hooks: make(logrus.LevelHooks),
|
||||
Level: level,
|
||||
}
|
||||
return logrus.NewEntry(l)
|
||||
}
|
||||
return log[0]
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if c.Family != "FreeBSD" {
|
||||
// set pipefail option. Bash only
|
||||
// http://unix.stackexchange.com/questions/14270/get-exit-status-of-process-thats-piped-to-another
|
||||
cmd = fmt.Sprintf("set -o pipefail; %s", cmd)
|
||||
}
|
||||
|
||||
if c.IsContainer() {
|
||||
switch c.Container.Type {
|
||||
case "", "docker":
|
||||
cmd = fmt.Sprintf(`docker exec %s /bin/bash -c "%s"`, c.Container.ContainerID, cmd)
|
||||
}
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func getAgentAuth() (auth ssh.AuthMethod, ok bool) {
|
||||
if sock := os.Getenv("SSH_AUTH_SOCK"); len(sock) > 0 {
|
||||
if agconn, err := net.Dial("unix", sock); err == nil {
|
||||
@@ -317,7 +401,7 @@ func parsePemBlock(block *pem.Block) (interface{}, error) {
|
||||
case "DSA PRIVATE KEY":
|
||||
return ssh.ParseDSAPrivateKey(block.Bytes)
|
||||
default:
|
||||
return nil, fmt.Errorf("rtop: unsupported key type %q", block.Type)
|
||||
return nil, fmt.Errorf("Unsupported key type %q", block.Type)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user