Support scanning with external ssh command

This commit is contained in:
kota kanbe
2016-06-16 19:59:30 +09:00
parent 5e28ec22e1
commit 0ef1a5a3ce
6 changed files with 195 additions and 81 deletions

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}
}