diff --git a/communicator/ssh/connect.go b/communicator/ssh/connect.go index 43277595c..7622a90e3 100644 --- a/communicator/ssh/connect.go +++ b/communicator/ssh/connect.go @@ -6,6 +6,7 @@ import ( "time" "golang.org/x/crypto/ssh" + "golang.org/x/net/proxy" ) // ConnectFunc is a convenience method for returning a function @@ -27,6 +28,25 @@ func ConnectFunc(network, addr string) func() (net.Conn, error) { } } +// ConnectFunc is a convenience method for returning a function +// that connects to a host using SOCKS5 proxy +func ProxyConnectFunc(socksProxy string, socksAuth *proxy.Auth, network, addr string) func() (net.Conn, error) { + return func() (net.Conn, error) { + // create a socks5 dialer + dialer, err := proxy.SOCKS5("tcp", socksProxy, socksAuth, proxy.Direct) + if err != nil { + return nil, fmt.Errorf("Can't connect to the proxy: %s", err) + } + + c, err := dialer.Dial(network, addr) + if err != nil { + return nil, err + } + + return c, nil + } +} + // BastionConnectFunc is a convenience method for returning a function // that connects to a host over a bastion connection. func BastionConnectFunc( diff --git a/helper/communicator/config.go b/helper/communicator/config.go index 243ca224d..d89a0ac27 100644 --- a/helper/communicator/config.go +++ b/helper/communicator/config.go @@ -33,6 +33,10 @@ type Config struct { SSHBastionPassword string `mapstructure:"ssh_bastion_password"` SSHBastionPrivateKey string `mapstructure:"ssh_bastion_private_key_file"` SSHFileTransferMethod string `mapstructure:"ssh_file_transfer_method"` + SSHProxyHost string `mapstructure:"ssh_proxy_host"` + SSHProxyPort int `mapstructure:"ssh_proxy_port"` + SSHProxyUsername string `mapstructure:"ssh_proxy_username"` + SSHProxyPassword string `mapstructure:"ssh_proxy_password"` // WinRM WinRMUser string `mapstructure:"winrm_username"` @@ -141,6 +145,12 @@ func (c *Config) prepareSSH(ctx *interpolate.Context) []error { } } + if c.SSHProxyHost != "" { + if c.SSHProxyPort == 0 { + c.SSHProxyPort = 1080 + } + } + if c.SSHFileTransferMethod == "" { c.SSHFileTransferMethod = "scp" } diff --git a/helper/communicator/step_connect_ssh.go b/helper/communicator/step_connect_ssh.go index 669d52410..9179f57e8 100644 --- a/helper/communicator/step_connect_ssh.go +++ b/helper/communicator/step_connect_ssh.go @@ -15,6 +15,7 @@ import ( "github.com/mitchellh/multistep" gossh "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" + "golang.org/x/net/proxy" ) // StepConnectSSH is a step that only connects to SSH. @@ -88,6 +89,8 @@ func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan stru // do this one before entering the retry loop. var bProto, bAddr string var bConf *gossh.ClientConfig + var pAddr string + var pAuth *proxy.Auth if s.Config.SSHBastionHost != "" { // The protocol is hardcoded for now, but may be configurable one day bProto = "tcp" @@ -101,6 +104,16 @@ func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan stru bConf = conf } + if s.Config.SSHProxyHost != "" { + pAddr = fmt.Sprintf("%s:%d", s.Config.SSHProxyHost, s.Config.SSHProxyPort) + if s.Config.SSHProxyUsername != "" { + pAuth = new(proxy.Auth) + pAuth.User = s.Config.SSHBastionUsername + pAuth.Password = s.Config.SSHBastionPassword + } + + } + handshakeAttempts := 0 var comm packer.Communicator @@ -146,6 +159,9 @@ func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan stru // We're using a bastion host, so use the bastion connfunc connFunc = ssh.BastionConnectFunc( bProto, bAddr, bConf, "tcp", address) + } else if pAddr != "" { + // Connect via SOCKS5 proxy + connFunc = ssh.ProxyConnectFunc(pAddr, pAuth, "tcp", address) } else { // No bastion host, connect directly connFunc = ssh.ConnectFunc("tcp", address)