2013-08-21 14:05:21 -04:00
|
|
|
// +build !race
|
|
|
|
|
2013-05-20 18:47:41 -04:00
|
|
|
package ssh
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2014-04-10 04:48:55 -04:00
|
|
|
"fmt"
|
2013-05-20 18:47:41 -04:00
|
|
|
"net"
|
|
|
|
"testing"
|
2015-07-02 06:40:47 -04:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/mitchellh/packer/packer"
|
|
|
|
"golang.org/x/crypto/ssh"
|
2013-05-20 18:47:41 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
// private key for mock server
|
|
|
|
const testServerPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
|
|
|
|
MIIEpAIBAAKCAQEA19lGVsTqIT5iiNYRgnoY1CwkbETW5cq+Rzk5v/kTlf31XpSU
|
|
|
|
70HVWkbTERECjaYdXM2gGcbb+sxpq6GtXf1M3kVomycqhxwhPv4Cr6Xp4WT/jkFx
|
|
|
|
9z+FFzpeodGJWjOH6L2H5uX1Cvr9EDdQp9t9/J32/qBFntY8GwoUI/y/1MSTmMiF
|
|
|
|
tupdMODN064vd3gyMKTwrlQ8tZM6aYuyOPsutLlUY7M5x5FwMDYvnPDSeyT/Iw0z
|
|
|
|
s3B+NCyqeeMd2T7YzQFnRATj0M7rM5LoSs7DVqVriOEABssFyLj31PboaoLhOKgc
|
|
|
|
qoM9khkNzr7FHVvi+DhYM2jD0DwvqZLN6NmnLwIDAQABAoIBAQCGVj+kuSFOV1lT
|
|
|
|
+IclQYA6bM6uY5mroqcSBNegVxCNhWU03BxlW//BE9tA/+kq53vWylMeN9mpGZea
|
|
|
|
riEMIh25KFGWXqXlOOioH8bkMsqA8S7sBmc7jljyv+0toQ9vCCtJ+sueNPhxQQxH
|
|
|
|
D2YvUjfzBQ04I9+wn30BByDJ1QA/FoPsunxIOUCcRBE/7jxuLYcpR+JvEF68yYIh
|
|
|
|
atXRld4W4in7T65YDR8jK1Uj9XAcNeDYNpT/M6oFLx1aPIlkG86aCWRO19S1jLPT
|
|
|
|
b1ZAKHHxPMCVkSYW0RqvIgLXQOR62D0Zne6/2wtzJkk5UCjkSQ2z7ZzJpMkWgDgN
|
|
|
|
ifCULFPBAoGBAPoMZ5q1w+zB+knXUD33n1J+niN6TZHJulpf2w5zsW+m2K6Zn62M
|
|
|
|
MXndXlVAHtk6p02q9kxHdgov34Uo8VpuNjbS1+abGFTI8NZgFo+bsDxJdItemwC4
|
|
|
|
KJ7L1iz39hRN/ZylMRLz5uTYRGddCkeIHhiG2h7zohH/MaYzUacXEEy3AoGBANz8
|
|
|
|
e/msleB+iXC0cXKwds26N4hyMdAFE5qAqJXvV3S2W8JZnmU+sS7vPAWMYPlERPk1
|
|
|
|
D8Q2eXqdPIkAWBhrx4RxD7rNc5qFNcQWEhCIxC9fccluH1y5g2M+4jpMX2CT8Uv+
|
|
|
|
3z+NoJ5uDTXZTnLCfoZzgZ4nCZVZ+6iU5U1+YXFJAoGBANLPpIV920n/nJmmquMj
|
|
|
|
orI1R/QXR9Cy56cMC65agezlGOfTYxk5Cfl5Ve+/2IJCfgzwJyjWUsFx7RviEeGw
|
|
|
|
64o7JoUom1HX+5xxdHPsyZ96OoTJ5RqtKKoApnhRMamau0fWydH1yeOEJd+TRHhc
|
|
|
|
XStGfhz8QNa1dVFvENczja1vAoGABGWhsd4VPVpHMc7lUvrf4kgKQtTC2PjA4xoc
|
|
|
|
QJ96hf/642sVE76jl+N6tkGMzGjnVm4P2j+bOy1VvwQavKGoXqJBRd5Apppv727g
|
|
|
|
/SM7hBXKFc/zH80xKBBgP/i1DR7kdjakCoeu4ngeGywvu2jTS6mQsqzkK+yWbUxJ
|
|
|
|
I7mYBsECgYB/KNXlTEpXtz/kwWCHFSYA8U74l7zZbVD8ul0e56JDK+lLcJ0tJffk
|
|
|
|
gqnBycHj6AhEycjda75cs+0zybZvN4x65KZHOGW/O/7OAWEcZP5TPb3zf9ned3Hl
|
|
|
|
NsZoFj52ponUM6+99A2CmezFCN16c4mbA//luWF+k3VVqR6BpkrhKw==
|
|
|
|
-----END RSA PRIVATE KEY-----`
|
|
|
|
|
|
|
|
var serverConfig = &ssh.ServerConfig{
|
2014-04-10 04:48:55 -04:00
|
|
|
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
|
|
|
|
if c.User() == "user" && string(pass) == "pass" {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("password rejected for %q", c.User())
|
2013-05-20 18:47:41 -04:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
2014-04-10 04:48:55 -04:00
|
|
|
// Parse and set the private key of the server, required to accept connections
|
|
|
|
signer, err := ssh.ParsePrivateKey([]byte(testServerPrivateKey))
|
|
|
|
if err != nil {
|
|
|
|
panic("unable to parse private key: " + err.Error())
|
2013-05-20 18:47:41 -04:00
|
|
|
}
|
2014-04-10 04:48:55 -04:00
|
|
|
serverConfig.AddHostKey(signer)
|
2013-05-20 18:47:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func newMockLineServer(t *testing.T) string {
|
2014-04-10 04:48:55 -04:00
|
|
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
2013-05-20 18:47:41 -04:00
|
|
|
if err != nil {
|
2014-04-10 04:48:55 -04:00
|
|
|
t.Fatalf("Unable to listen for connection: %s", err)
|
2013-05-20 18:47:41 -04:00
|
|
|
}
|
2014-04-10 04:48:55 -04:00
|
|
|
|
2013-05-20 18:47:41 -04:00
|
|
|
go func() {
|
|
|
|
defer l.Close()
|
|
|
|
c, err := l.Accept()
|
|
|
|
if err != nil {
|
2014-04-10 04:48:55 -04:00
|
|
|
t.Errorf("Unable to accept incoming connection: %s", err)
|
2013-05-20 18:47:41 -04:00
|
|
|
}
|
2014-04-10 04:48:55 -04:00
|
|
|
defer c.Close()
|
|
|
|
conn, chans, _, err := ssh.NewServerConn(c, serverConfig)
|
|
|
|
if err != nil {
|
2013-05-20 18:47:41 -04:00
|
|
|
t.Logf("Handshaking error: %v", err)
|
|
|
|
}
|
|
|
|
t.Log("Accepted SSH connection")
|
2014-04-10 04:48:55 -04:00
|
|
|
for newChannel := range chans {
|
|
|
|
channel, _, err := newChannel.Accept()
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Unable to accept channel.")
|
|
|
|
}
|
|
|
|
t.Log("Accepted channel")
|
|
|
|
|
2014-12-15 13:54:44 -05:00
|
|
|
go func(channelType string) {
|
2014-04-10 04:48:55 -04:00
|
|
|
defer channel.Close()
|
2014-12-15 13:54:44 -05:00
|
|
|
conn.OpenChannel(channelType, nil)
|
|
|
|
}(newChannel.ChannelType())
|
2013-05-20 18:47:41 -04:00
|
|
|
}
|
2014-04-10 04:48:55 -04:00
|
|
|
conn.Close()
|
2013-05-20 18:47:41 -04:00
|
|
|
}()
|
2014-04-10 04:48:55 -04:00
|
|
|
|
2013-05-20 18:47:41 -04:00
|
|
|
return l.Addr().String()
|
|
|
|
}
|
|
|
|
|
2015-07-02 06:40:47 -04:00
|
|
|
func newMockBrokenServer(t *testing.T) string {
|
|
|
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Unable tp listen for connection: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
defer l.Close()
|
|
|
|
c, err := l.Accept()
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Unable to accept incoming connection: %s", err)
|
|
|
|
}
|
|
|
|
defer c.Close()
|
|
|
|
// This should block for a period of time longer than our timeout in
|
|
|
|
// the test case. That way we invoke a failure scenario.
|
|
|
|
t.Log("Block on handshaking for SSH connection")
|
2015-07-02 06:55:18 -04:00
|
|
|
time.Sleep(5 * time.Second)
|
2015-07-02 06:40:47 -04:00
|
|
|
}()
|
|
|
|
|
|
|
|
return l.Addr().String()
|
|
|
|
}
|
|
|
|
|
2013-05-20 18:52:34 -04:00
|
|
|
func TestCommIsCommunicator(t *testing.T) {
|
|
|
|
var raw interface{}
|
|
|
|
raw = &comm{}
|
|
|
|
if _, ok := raw.(packer.Communicator); !ok {
|
|
|
|
t.Fatalf("comm must be a communicator")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-20 18:47:41 -04:00
|
|
|
func TestNew_Invalid(t *testing.T) {
|
|
|
|
clientConfig := &ssh.ClientConfig{
|
|
|
|
User: "user",
|
2014-04-10 04:48:55 -04:00
|
|
|
Auth: []ssh.AuthMethod{
|
|
|
|
ssh.Password("i-am-invalid"),
|
2013-05-20 18:47:41 -04:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2014-04-10 04:48:55 -04:00
|
|
|
address := newMockLineServer(t)
|
2013-07-14 07:22:41 -04:00
|
|
|
conn := func() (net.Conn, error) {
|
2014-04-10 04:48:55 -04:00
|
|
|
conn, err := net.Dial("tcp", address)
|
2013-07-14 07:22:41 -04:00
|
|
|
if err != nil {
|
2014-04-10 04:48:55 -04:00
|
|
|
t.Errorf("Unable to accept incoming connection: %v", err)
|
2013-07-14 07:22:41 -04:00
|
|
|
}
|
|
|
|
return conn, err
|
|
|
|
}
|
|
|
|
|
|
|
|
config := &Config{
|
|
|
|
Connection: conn,
|
2013-07-14 07:55:27 -04:00
|
|
|
SSHConfig: clientConfig,
|
2013-05-20 18:47:41 -04:00
|
|
|
}
|
|
|
|
|
2014-04-10 04:48:55 -04:00
|
|
|
_, err := New(address, config)
|
2013-05-20 18:47:41 -04:00
|
|
|
if err == nil {
|
|
|
|
t.Fatal("should have had an error connecting")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestStart(t *testing.T) {
|
|
|
|
clientConfig := &ssh.ClientConfig{
|
|
|
|
User: "user",
|
2014-04-10 04:48:55 -04:00
|
|
|
Auth: []ssh.AuthMethod{
|
|
|
|
ssh.Password("pass"),
|
2013-05-20 18:47:41 -04:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2014-04-10 04:48:55 -04:00
|
|
|
address := newMockLineServer(t)
|
2013-07-14 07:22:41 -04:00
|
|
|
conn := func() (net.Conn, error) {
|
2014-04-10 04:48:55 -04:00
|
|
|
conn, err := net.Dial("tcp", address)
|
2013-07-14 07:22:41 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to dial to remote side: %s", err)
|
|
|
|
}
|
|
|
|
return conn, err
|
|
|
|
}
|
|
|
|
|
|
|
|
config := &Config{
|
|
|
|
Connection: conn,
|
2013-07-14 07:55:27 -04:00
|
|
|
SSHConfig: clientConfig,
|
2013-05-20 18:47:41 -04:00
|
|
|
}
|
|
|
|
|
2014-04-10 04:48:55 -04:00
|
|
|
client, err := New(address, config)
|
2013-05-20 18:47:41 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error connecting to SSH: %s", err)
|
|
|
|
}
|
|
|
|
|
2015-07-02 06:40:47 -04:00
|
|
|
cmd := &packer.RemoteCmd{
|
|
|
|
Command: "echo foo",
|
|
|
|
Stdout: new(bytes.Buffer),
|
|
|
|
}
|
|
|
|
|
|
|
|
client.Start(cmd)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestHandshakeTimeout(t *testing.T) {
|
|
|
|
clientConfig := &ssh.ClientConfig{
|
|
|
|
User: "user",
|
|
|
|
Auth: []ssh.AuthMethod{
|
|
|
|
ssh.Password("pass"),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
address := newMockBrokenServer(t)
|
|
|
|
conn := func() (net.Conn, error) {
|
|
|
|
conn, err := net.Dial("tcp", address)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to dial to remote side: %s", err)
|
|
|
|
}
|
|
|
|
return conn, err
|
|
|
|
}
|
|
|
|
|
|
|
|
config := &Config{
|
|
|
|
Connection: conn,
|
|
|
|
SSHConfig: clientConfig,
|
|
|
|
HandshakeTimeout: 50 * time.Millisecond,
|
|
|
|
}
|
2013-06-03 02:27:01 -04:00
|
|
|
|
2015-07-02 06:40:47 -04:00
|
|
|
_, err := New(address, config)
|
|
|
|
if err != ErrHandshakeTimeout {
|
|
|
|
// Note: there's another error that can come back from this call:
|
|
|
|
// ssh: handshake failed: EOF
|
|
|
|
// This should appear in cases where the handshake fails because of
|
|
|
|
// malformed (or no) data sent back by the server, but should not happen
|
|
|
|
// in a timeout scenario.
|
|
|
|
t.Fatalf("Expected handshake timeout, got: %s", err)
|
|
|
|
}
|
2013-05-20 18:47:41 -04:00
|
|
|
}
|