* Change config.toml, Auto-generate UUIDs, change structure of optional field * Detect processes affected by update using yum-ps (#482) Detect processes affected by update using yum-ps * Detect processes needs restart using checkrestart on Debian and Ubuntu. * pass cpename by args when calling FillCveInfo (#513) * fix new db (#502) * Include Version,Revision in JSON * Include hostname in JSON * Update goval-dictionary's commit hash in Gopkg.lock * Remove README.ja.md * update packages (#596) * fix: change ControlPath to .vuls of SSH option (#618) * feat: checkrestart for Ubuntu and Debian (#622) * feat: checkrestart for Ubuntu and Debian * fix: dependencies check logic of configtest * feat: need-restarting on RedHat * refactor: Process.ProcName to Process.Name * feat: detect a systemd service name of need-restarting-process * feat: detect a systemd service name of need-restarting-process on Ubuntu * feat: fill a service name of need-restarting-process, init-system * Support NVD JSON and CVSS3 of JVN (#605) * fix: compile errors * fix: Show CVSS3 on TUI * fix: test cases * fix: Avoid null in JSON * Fix maxCvssScore (#621) * Fix maxCvssScore * Update vulninfos.go * fix(init): remove unnecessary log initialization * refactor(nvd): use only json feed if exists json data. if not, use xml feed * fix(scan): make Confidence slice * feat(CWE): Display CWE name to TUI * feat(cwe): import CWE defs in Japanese * feat(cwe): add OWASP Top 10 ranking to CWE if applicable * feat(scan): add -fast-root mode, implement scan/amazon.go * refactor(const): change const name JVN to Jvn * feat(scan): add -fast-root mode, implement scan/centos.go * refactor(dep): update deps * fix(amazon): deps check * feat(scan): add -fast-root mode, implement scan/rhel.go * feat(scan): add -fast-root mode, implement scan/oracle.go * fix complile err * feat(scan): add -fast-root mode, implement scan/debian.go * fix testcase * fix(amazon): scan using yum * fix(configtest): change error message, status when no scannnable servers * Fix(scan): detect init process logic * fix(tui): display cvss as table format * fix(scan): parse a output of reboot-notifier on CentOS6.9 * fix(tui): don't display score, vector when score is zero * fix(scan): add -offline mode to suse scanner * fix(scan): fix help message * feat(scan): enable to define scan mode for each servers in config.toml #510 * refactor(config): chagne cpeNames to cpeURIs * refactor(config): change dependencyCheckXMLPath to owaspDCXMLPath * fix(config): containers -> containersIncluded, Excluded, containerType * feature(report): enable to define cpeURIs for each contaner * feature(report): enable to specify owasp dc xml path for each container * fix(discover): fix a template displayed at the end of discover * feature(report): add ignorePkgsRegexp #665 * feature(report): enable to define ignoreCves for each container #666 * fix(report): Displayed nothing in TUI detail area when CweID is nil * Gopkg.toml diet * feat(server): support server mode (#678) * feat(server): support server mode * Lock go version * Use the latest kernel release among the installed release when the running kernel release is unknown * Add TestViaHTTP * Set logger to go-cve-dictionary client * Add -to-localfile * Add -to-http option to report * Load -to-http conf from config.toml * Support gost (#676) * feat(gost): Support RedHat API * feat(gost): Support Debian Security Tracker * feat(db): display error msg when SQLite3 is locked at the beginning of reporting. * feat(gost): TUI * Only use RedHat information of installed packages * feat(tui): show mitigation on TUI * feat(gost): support redis backend * fix test case * fix nil pointer when db is nil * fix(gost): detect vulns of src packages for Debian * feat(gost): implement redis backend for gost redhat api * feat(report): display fixState of unfixed pkgs * fix(report): display distincted cweIDs * feat(slack): display gost info * feat(slack): display mitigation * feat(report): display available patch state as fixed/total * fix(tui): display - if source of reference is empty * update deps * fix(report): key in ScanResult JSON be lowerCamelcase. * some keys to lower camel * fix(configtest): dep check logic of yum-plugin-ps * fix(tui): format * feat(report): add -format-list option * fix(report): -format-full-text * fix(report): report -format-full-text * fix(report): display v3 score detected by gost * fix(scan): scan in fast mode if not defined in config.toml * fix(gost): fetch RedHat data for fixed CVEs * feat(report): show number of cves detected in each database * fix(report): show new version as `Unknown` in offline and fast scan mode * fix(report): fix num of upadtable and fixed * fix(report): set `Not fixed yet` if packageStatus is empty * refact(gost): make convertToModel public * fix(test): fix test case * update deps * fix(report): include gost score in MaxCvssScore * [WIP] feat(config): enable to set options in config.toml instead of cmd opt (#690) * feat(config): enable to set options in config.toml instead of cmd opt * fix(config): change Conf.Report.Slack to Conf.Slack * fix(discover): change tempalte * fix(report): fix config.toml auto-generate with -uuid * Add endpoint for health check and change endpoint * refact(cmd): refactor flag set * fix(report): enable to specify opts with cmd arg and env value * fix(scan): enable to parse the release version of amazon linux 2 * add(report) add -to-saas option (#695) * add(report) add -to-saas option * ignore other writer if -to-saas * fix(saas) fix bug * fix(scan): need-restarting needs internet connection * fix(scan,configtest): check scan mode * refactor(scan): change func name * fix(suse): support offline mode, bug fix on AWS, zypper --no-color * fix(tui): fix nil pointer when no vulns in tui * feat(report): enable to define CPE FS format in config.toml * fix(vet): fix warnings of go vet * fix(travis): go version to 1.11 * update deps
		
			
				
	
	
		
			505 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			505 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/* Vuls - Vulnerability Scanner
 | 
						|
Copyright (C) 2016  Future Corporation , Japan.
 | 
						|
 | 
						|
This program is free software: you can redistribute it and/or modify
 | 
						|
it under the terms of the GNU General Public License as published by
 | 
						|
the Free Software Foundation, either version 3 of the License, or
 | 
						|
(at your option) any later version.
 | 
						|
 | 
						|
This program is distributed in the hope that it will be useful,
 | 
						|
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
GNU General Public License for more details.
 | 
						|
 | 
						|
You should have received a copy of the GNU General Public License
 | 
						|
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
						|
*/
 | 
						|
 | 
						|
package scan
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"crypto/x509"
 | 
						|
	"encoding/pem"
 | 
						|
	"fmt"
 | 
						|
	"io/ioutil"
 | 
						|
	"net"
 | 
						|
	"os"
 | 
						|
	ex "os/exec"
 | 
						|
	"path/filepath"
 | 
						|
	"strings"
 | 
						|
	"syscall"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"golang.org/x/crypto/ssh"
 | 
						|
	"golang.org/x/crypto/ssh/agent"
 | 
						|
 | 
						|
	"github.com/cenkalti/backoff"
 | 
						|
	conf "github.com/future-architect/vuls/config"
 | 
						|
	"github.com/future-architect/vuls/util"
 | 
						|
	homedir "github.com/mitchellh/go-homedir"
 | 
						|
	"github.com/sirupsen/logrus"
 | 
						|
)
 | 
						|
 | 
						|
type execResult struct {
 | 
						|
	Servername string
 | 
						|
	Container  conf.Container
 | 
						|
	Host       string
 | 
						|
	Port       string
 | 
						|
	Cmd        string
 | 
						|
	Stdout     string
 | 
						|
	Stderr     string
 | 
						|
	ExitStatus int
 | 
						|
	Error      error
 | 
						|
}
 | 
						|
 | 
						|
func (s execResult) String() string {
 | 
						|
	sname := ""
 | 
						|
	if s.Container.ContainerID == "" {
 | 
						|
		sname = s.Servername
 | 
						|
	} else {
 | 
						|
		sname = s.Container.Name + "@" + s.Servername
 | 
						|
	}
 | 
						|
 | 
						|
	return fmt.Sprintf(
 | 
						|
		"execResult: servername: %s\n  cmd: %s\n  exitstatus: %d\n  stdout: %s\n  stderr: %s\n  err: %s",
 | 
						|
		sname, s.Cmd, s.ExitStatus, s.Stdout, s.Stderr, s.Error)
 | 
						|
}
 | 
						|
 | 
						|
func (s execResult) isSuccess(expectedStatusCodes ...int) bool {
 | 
						|
	if len(expectedStatusCodes) == 0 {
 | 
						|
		return s.ExitStatus == 0
 | 
						|
	}
 | 
						|
	for _, code := range expectedStatusCodes {
 | 
						|
		if code == s.ExitStatus {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if s.Error != nil {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
// sudo is Const value for sudo mode
 | 
						|
const sudo = true
 | 
						|
 | 
						|
// noSudo is Const value for normal user mode
 | 
						|
const noSudo = false
 | 
						|
 | 
						|
//  Issue commands to the target servers in parallel via SSH or local execution.  If execution fails, the server will be excluded from the target server list(servers) and added to the error server list(errServers).
 | 
						|
func parallelExec(fn func(osTypeInterface) error, timeoutSec ...int) {
 | 
						|
	resChan := make(chan osTypeInterface, len(servers))
 | 
						|
	defer close(resChan)
 | 
						|
 | 
						|
	for _, s := range servers {
 | 
						|
		go func(s osTypeInterface) {
 | 
						|
			defer func() {
 | 
						|
				if p := recover(); p != nil {
 | 
						|
					util.Log.Debugf("Panic: %s on %s",
 | 
						|
						p, s.getServerInfo().GetServerName())
 | 
						|
				}
 | 
						|
			}()
 | 
						|
			if err := fn(s); err != nil {
 | 
						|
				s.setErrs([]error{err})
 | 
						|
				resChan <- s
 | 
						|
			} else {
 | 
						|
				resChan <- s
 | 
						|
			}
 | 
						|
		}(s)
 | 
						|
	}
 | 
						|
 | 
						|
	var timeout int
 | 
						|
	if len(timeoutSec) == 0 {
 | 
						|
		timeout = 10 * 60
 | 
						|
	} else {
 | 
						|
		timeout = timeoutSec[0]
 | 
						|
	}
 | 
						|
 | 
						|
	var successes []osTypeInterface
 | 
						|
	isTimedout := false
 | 
						|
	for i := 0; i < len(servers); i++ {
 | 
						|
		select {
 | 
						|
		case s := <-resChan:
 | 
						|
			if len(s.getErrs()) == 0 {
 | 
						|
				successes = append(successes, s)
 | 
						|
			} else {
 | 
						|
				util.Log.Errorf("Error: %s, err: %s",
 | 
						|
					s.getServerInfo().GetServerName(), s.getErrs())
 | 
						|
				errServers = append(errServers, s)
 | 
						|
			}
 | 
						|
		case <-time.After(time.Duration(timeout) * time.Second):
 | 
						|
			isTimedout = true
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if isTimedout {
 | 
						|
		// set timed out error and append to errServers
 | 
						|
		for _, s := range servers {
 | 
						|
			name := s.getServerInfo().GetServerName()
 | 
						|
			found := false
 | 
						|
			for _, ss := range successes {
 | 
						|
				if name == ss.getServerInfo().GetServerName() {
 | 
						|
					found = true
 | 
						|
					break
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if !found {
 | 
						|
				msg := fmt.Sprintf("Timed out: %s",
 | 
						|
					s.getServerInfo().GetServerName())
 | 
						|
				util.Log.Errorf(msg)
 | 
						|
				s.setErrs([]error{fmt.Errorf(msg)})
 | 
						|
				errServers = append(errServers, s)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	servers = successes
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
func exec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (result execResult) {
 | 
						|
	logger := getSSHLogger(log...)
 | 
						|
	logger.Debugf("Executing... %s", strings.Replace(cmd, "\n", "", -1))
 | 
						|
 | 
						|
	if c.Port == "local" &&
 | 
						|
		(c.Host == "127.0.0.1" || c.Host == "localhost") {
 | 
						|
		result = localExec(c, cmd, sudo)
 | 
						|
	} else if conf.Conf.SSHNative {
 | 
						|
		result = sshExecNative(c, cmd, sudo)
 | 
						|
	} else {
 | 
						|
		result = sshExecExternal(c, cmd, sudo)
 | 
						|
	}
 | 
						|
 | 
						|
	logger.Debug(result)
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
func localExec(c conf.ServerInfo, cmdstr string, sudo bool) (result execResult) {
 | 
						|
	cmdstr = decorateCmd(c, cmdstr, sudo)
 | 
						|
	var cmd *ex.Cmd
 | 
						|
	switch c.Distro.Family {
 | 
						|
	// case conf.FreeBSD, conf.Alpine, conf.Debian:
 | 
						|
	// cmd = ex.Command("/bin/sh", "-c", cmdstr)
 | 
						|
	default:
 | 
						|
		cmd = ex.Command("/bin/sh", "-c", cmdstr)
 | 
						|
	}
 | 
						|
	var stdoutBuf, stderrBuf bytes.Buffer
 | 
						|
	cmd.Stdout = &stdoutBuf
 | 
						|
	cmd.Stderr = &stderrBuf
 | 
						|
 | 
						|
	if err := cmd.Run(); err != nil {
 | 
						|
		result.Error = err
 | 
						|
		if exitError, ok := err.(*ex.ExitError); ok {
 | 
						|
			waitStatus := exitError.Sys().(syscall.WaitStatus)
 | 
						|
			result.ExitStatus = waitStatus.ExitStatus()
 | 
						|
		} else {
 | 
						|
			result.ExitStatus = 999
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		result.ExitStatus = 0
 | 
						|
	}
 | 
						|
 | 
						|
	result.Stdout = stdoutBuf.String()
 | 
						|
	result.Stderr = stderrBuf.String()
 | 
						|
	result.Cmd = strings.Replace(cmdstr, "\n", "", -1)
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
func sshExecNative(c conf.ServerInfo, cmd string, sudo bool) (result execResult) {
 | 
						|
	result.Servername = c.ServerName
 | 
						|
	result.Container = c.Container
 | 
						|
	result.Host = c.Host
 | 
						|
	result.Port = c.Port
 | 
						|
 | 
						|
	var client *ssh.Client
 | 
						|
	var err error
 | 
						|
	if client, err = sshConnect(c); err != nil {
 | 
						|
		result.Error = err
 | 
						|
		result.ExitStatus = 999
 | 
						|
		return
 | 
						|
	}
 | 
						|
	defer client.Close()
 | 
						|
 | 
						|
	var session *ssh.Session
 | 
						|
	if session, err = client.NewSession(); err != nil {
 | 
						|
		result.Error = fmt.Errorf(
 | 
						|
			"Failed to create a new session. servername: %s, err: %s",
 | 
						|
			c.ServerName, err)
 | 
						|
		result.ExitStatus = 999
 | 
						|
		return
 | 
						|
	}
 | 
						|
	defer session.Close()
 | 
						|
 | 
						|
	// http://blog.ralch.com/tutorial/golang-ssh-connection/
 | 
						|
	modes := ssh.TerminalModes{
 | 
						|
		ssh.ECHO:          0,     // disable echoing
 | 
						|
		ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
 | 
						|
		ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
 | 
						|
	}
 | 
						|
	if err = session.RequestPty("xterm", 400, 1000, modes); err != nil {
 | 
						|
		result.Error = fmt.Errorf(
 | 
						|
			"Failed to request for pseudo terminal. servername: %s, err: %s",
 | 
						|
			c.ServerName, err)
 | 
						|
		result.ExitStatus = 999
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	var stdoutBuf, stderrBuf bytes.Buffer
 | 
						|
	session.Stdout = &stdoutBuf
 | 
						|
	session.Stderr = &stderrBuf
 | 
						|
 | 
						|
	cmd = decorateCmd(c, cmd, sudo)
 | 
						|
	if err := session.Run(cmd); err != nil {
 | 
						|
		if exitErr, ok := err.(*ssh.ExitError); ok {
 | 
						|
			result.ExitStatus = exitErr.ExitStatus()
 | 
						|
		} else {
 | 
						|
			result.ExitStatus = 999
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		result.ExitStatus = 0
 | 
						|
	}
 | 
						|
 | 
						|
	result.Stdout = stdoutBuf.String()
 | 
						|
	result.Stderr = stderrBuf.String()
 | 
						|
	result.Cmd = strings.Replace(cmd, "\n", "", -1)
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
func sshExecExternal(c conf.ServerInfo, cmd string, sudo bool) (result execResult) {
 | 
						|
	sshBinaryPath, err := ex.LookPath("ssh")
 | 
						|
	if err != nil {
 | 
						|
		return sshExecNative(c, cmd, sudo)
 | 
						|
	}
 | 
						|
 | 
						|
	defaultSSHArgs := []string{"-tt"}
 | 
						|
 | 
						|
	if !conf.Conf.SSHConfig {
 | 
						|
		home, err := homedir.Dir()
 | 
						|
		if err != nil {
 | 
						|
			msg := fmt.Sprintf("Failed to get HOME directory: %s", err)
 | 
						|
			result.Stderr = msg
 | 
						|
			result.ExitStatus = 997
 | 
						|
			return
 | 
						|
		}
 | 
						|
		controlPath := filepath.Join(home, ".vuls", `controlmaster-%r-`+c.ServerName+`.%p`)
 | 
						|
 | 
						|
		defaultSSHArgs = append(defaultSSHArgs,
 | 
						|
			"-o", "StrictHostKeyChecking=yes",
 | 
						|
			"-o", "LogLevel=quiet",
 | 
						|
			"-o", "ConnectionAttempts=3",
 | 
						|
			"-o", "ConnectTimeout=10",
 | 
						|
			"-o", "ControlMaster=auto",
 | 
						|
			"-o", fmt.Sprintf("ControlPath=%s", controlPath),
 | 
						|
			"-o", "Controlpersist=10m",
 | 
						|
		)
 | 
						|
	}
 | 
						|
 | 
						|
	if conf.Conf.Vvv {
 | 
						|
		defaultSSHArgs = append(defaultSSHArgs, "-vvv")
 | 
						|
	}
 | 
						|
 | 
						|
	args := append(defaultSSHArgs, fmt.Sprintf("%s@%s", c.User, c.Host))
 | 
						|
	args = append(args, "-p", c.Port)
 | 
						|
	if 0 < len(c.KeyPath) {
 | 
						|
		args = append(args, "-i", c.KeyPath)
 | 
						|
		args = append(args, "-o", "PasswordAuthentication=no")
 | 
						|
	}
 | 
						|
 | 
						|
	cmd = decorateCmd(c, cmd, sudo)
 | 
						|
	cmd = fmt.Sprintf("stty cols 1000; %s", cmd)
 | 
						|
 | 
						|
	args = append(args, cmd)
 | 
						|
	execCmd := ex.Command(sshBinaryPath, args...)
 | 
						|
 | 
						|
	var stdoutBuf, stderrBuf bytes.Buffer
 | 
						|
	execCmd.Stdout = &stdoutBuf
 | 
						|
	execCmd.Stderr = &stderrBuf
 | 
						|
	if err := execCmd.Run(); err != nil {
 | 
						|
		if e, ok := err.(*ex.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.Servername = c.ServerName
 | 
						|
	result.Container = c.Container
 | 
						|
	result.Host = c.Host
 | 
						|
	result.Port = c.Port
 | 
						|
	result.Cmd = fmt.Sprintf("%s %s", sshBinaryPath, strings.Join(args, " "))
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
func getSSHLogger(log ...*logrus.Entry) *logrus.Entry {
 | 
						|
	if len(log) == 0 {
 | 
						|
		return util.NewCustomLogger(conf.ServerInfo{})
 | 
						|
	}
 | 
						|
	return log[0]
 | 
						|
}
 | 
						|
 | 
						|
func dockerShell(family string) string {
 | 
						|
	switch family {
 | 
						|
	// case conf.Alpine, conf.Debian:
 | 
						|
	// return "/bin/sh"
 | 
						|
	default:
 | 
						|
		// return "/bin/bash"
 | 
						|
		return "/bin/sh"
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func decorateCmd(c conf.ServerInfo, cmd string, sudo bool) string {
 | 
						|
	if sudo && c.User != "root" && !c.IsContainer() {
 | 
						|
		cmd = fmt.Sprintf("sudo -S %s", cmd)
 | 
						|
	}
 | 
						|
 | 
						|
	// If you are using pipe and you want to detect preprocessing errors, remove comment out
 | 
						|
	//  switch c.Distro.Family {
 | 
						|
	//  case "FreeBSD", "ubuntu", "debian", "raspbian":
 | 
						|
	//  default:
 | 
						|
	//      // 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.ContainerType {
 | 
						|
		case "", "docker":
 | 
						|
			cmd = fmt.Sprintf(`docker exec --user 0 %s %s -c '%s'`,
 | 
						|
				c.Container.ContainerID, dockerShell(c.Distro.Family), cmd)
 | 
						|
		case "lxd":
 | 
						|
			// If the user belong to the "lxd" group, root privilege is not required.
 | 
						|
			cmd = fmt.Sprintf(`lxc exec %s -- %s -c '%s'`,
 | 
						|
				c.Container.Name, dockerShell(c.Distro.Family), cmd)
 | 
						|
		case "lxc":
 | 
						|
			cmd = fmt.Sprintf(`lxc-attach -n %s 2>/dev/null -- %s -c '%s'`,
 | 
						|
				c.Container.Name, dockerShell(c.Distro.Family), cmd)
 | 
						|
			// LXC required root privilege
 | 
						|
			if c.User != "root" {
 | 
						|
				cmd = fmt.Sprintf("sudo -S %s", cmd)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	//  cmd = fmt.Sprintf("set -x; %s", cmd)
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
func getAgentAuth() (auth ssh.AuthMethod, ok bool) {
 | 
						|
	if sock := os.Getenv("SSH_AUTH_SOCK"); 0 < len(sock) {
 | 
						|
		if agconn, err := net.Dial("unix", sock); err == nil {
 | 
						|
			ag := agent.NewClient(agconn)
 | 
						|
			auth = ssh.PublicKeysCallback(ag.Signers)
 | 
						|
			ok = true
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
func tryAgentConnect(c conf.ServerInfo) *ssh.Client {
 | 
						|
	if auth, ok := getAgentAuth(); ok {
 | 
						|
		config := &ssh.ClientConfig{
 | 
						|
			User:            c.User,
 | 
						|
			Auth:            []ssh.AuthMethod{auth},
 | 
						|
			HostKeyCallback: ssh.InsecureIgnoreHostKey(),
 | 
						|
		}
 | 
						|
		client, _ := ssh.Dial("tcp", c.Host+":"+c.Port, config)
 | 
						|
		return client
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func sshConnect(c conf.ServerInfo) (client *ssh.Client, err error) {
 | 
						|
	if client = tryAgentConnect(c); client != nil {
 | 
						|
		return client, nil
 | 
						|
	}
 | 
						|
 | 
						|
	var auths = []ssh.AuthMethod{}
 | 
						|
	if auths, err = addKeyAuth(auths, c.KeyPath, c.KeyPassword); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// http://blog.ralch.com/tutorial/golang-ssh-connection/
 | 
						|
	config := &ssh.ClientConfig{
 | 
						|
		User:            c.User,
 | 
						|
		Auth:            auths,
 | 
						|
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
 | 
						|
	}
 | 
						|
 | 
						|
	notifyFunc := func(e error, t time.Duration) {
 | 
						|
		logger := getSSHLogger()
 | 
						|
		logger.Debugf("Failed to Dial to %s, err: %s, Retrying in %s...",
 | 
						|
			c.ServerName, e, t)
 | 
						|
	}
 | 
						|
	err = backoff.RetryNotify(func() error {
 | 
						|
		if client, err = ssh.Dial("tcp", c.Host+":"+c.Port, config); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
	}, backoff.NewExponentialBackOff(), notifyFunc)
 | 
						|
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// https://github.com/rapidloop/rtop/blob/ba5b35e964135d50e0babedf0bd69b2fcb5dbcb4/src/sshhelper.go#L100
 | 
						|
func addKeyAuth(auths []ssh.AuthMethod, keypath string, keypassword string) ([]ssh.AuthMethod, error) {
 | 
						|
	if len(keypath) == 0 {
 | 
						|
		return auths, nil
 | 
						|
	}
 | 
						|
 | 
						|
	// read the file
 | 
						|
	pemBytes, err := ioutil.ReadFile(keypath)
 | 
						|
	if err != nil {
 | 
						|
		return auths, err
 | 
						|
	}
 | 
						|
 | 
						|
	// get first pem block
 | 
						|
	block, _ := pem.Decode(pemBytes)
 | 
						|
	if block == nil {
 | 
						|
		return auths, fmt.Errorf("no key found in %s", keypath)
 | 
						|
	}
 | 
						|
 | 
						|
	// handle plain and encrypted keyfiles
 | 
						|
	if x509.IsEncryptedPEMBlock(block) {
 | 
						|
		block.Bytes, err = x509.DecryptPEMBlock(block, []byte(keypassword))
 | 
						|
		if err != nil {
 | 
						|
			return auths, err
 | 
						|
		}
 | 
						|
		key, err := parsePemBlock(block)
 | 
						|
		if err != nil {
 | 
						|
			return auths, err
 | 
						|
		}
 | 
						|
		signer, err := ssh.NewSignerFromKey(key)
 | 
						|
		if err != nil {
 | 
						|
			return auths, err
 | 
						|
		}
 | 
						|
		return append(auths, ssh.PublicKeys(signer)), nil
 | 
						|
	}
 | 
						|
 | 
						|
	signer, err := ssh.ParsePrivateKey(pemBytes)
 | 
						|
	if err != nil {
 | 
						|
		return auths, err
 | 
						|
	}
 | 
						|
	return append(auths, ssh.PublicKeys(signer)), nil
 | 
						|
}
 | 
						|
 | 
						|
// ref golang.org/x/crypto/ssh/keys.go#ParseRawPrivateKey.
 | 
						|
func parsePemBlock(block *pem.Block) (interface{}, error) {
 | 
						|
	switch block.Type {
 | 
						|
	case "RSA PRIVATE KEY":
 | 
						|
		return x509.ParsePKCS1PrivateKey(block.Bytes)
 | 
						|
	case "EC PRIVATE KEY":
 | 
						|
		return x509.ParseECPrivateKey(block.Bytes)
 | 
						|
	case "DSA PRIVATE KEY":
 | 
						|
		return ssh.ParseDSAPrivateKey(block.Bytes)
 | 
						|
	default:
 | 
						|
		return nil, fmt.Errorf("Unsupported key type %q", block.Type)
 | 
						|
	}
 | 
						|
}
 |