github.com/iaas-resource-provision/iaas-rpc@v1.0.7-0.20211021023331-ed21f798c408/internal/communicator/ssh/communicator_test.go (about)

     1  // +build !race
     2  
     3  package ssh
     4  
     5  import (
     6  	"bufio"
     7  	"bytes"
     8  	"encoding/base64"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"math/rand"
    13  	"net"
    14  	"os"
    15  	"path/filepath"
    16  	"regexp"
    17  	"strconv"
    18  	"strings"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/iaas-resource-provision/iaas-rpc/internal/communicator/remote"
    23  	"github.com/zclconf/go-cty/cty"
    24  	"golang.org/x/crypto/ssh"
    25  )
    26  
    27  // private key for mock server
    28  const testServerPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
    29  MIIEpAIBAAKCAQEA19lGVsTqIT5iiNYRgnoY1CwkbETW5cq+Rzk5v/kTlf31XpSU
    30  70HVWkbTERECjaYdXM2gGcbb+sxpq6GtXf1M3kVomycqhxwhPv4Cr6Xp4WT/jkFx
    31  9z+FFzpeodGJWjOH6L2H5uX1Cvr9EDdQp9t9/J32/qBFntY8GwoUI/y/1MSTmMiF
    32  tupdMODN064vd3gyMKTwrlQ8tZM6aYuyOPsutLlUY7M5x5FwMDYvnPDSeyT/Iw0z
    33  s3B+NCyqeeMd2T7YzQFnRATj0M7rM5LoSs7DVqVriOEABssFyLj31PboaoLhOKgc
    34  qoM9khkNzr7FHVvi+DhYM2jD0DwvqZLN6NmnLwIDAQABAoIBAQCGVj+kuSFOV1lT
    35  +IclQYA6bM6uY5mroqcSBNegVxCNhWU03BxlW//BE9tA/+kq53vWylMeN9mpGZea
    36  riEMIh25KFGWXqXlOOioH8bkMsqA8S7sBmc7jljyv+0toQ9vCCtJ+sueNPhxQQxH
    37  D2YvUjfzBQ04I9+wn30BByDJ1QA/FoPsunxIOUCcRBE/7jxuLYcpR+JvEF68yYIh
    38  atXRld4W4in7T65YDR8jK1Uj9XAcNeDYNpT/M6oFLx1aPIlkG86aCWRO19S1jLPT
    39  b1ZAKHHxPMCVkSYW0RqvIgLXQOR62D0Zne6/2wtzJkk5UCjkSQ2z7ZzJpMkWgDgN
    40  ifCULFPBAoGBAPoMZ5q1w+zB+knXUD33n1J+niN6TZHJulpf2w5zsW+m2K6Zn62M
    41  MXndXlVAHtk6p02q9kxHdgov34Uo8VpuNjbS1+abGFTI8NZgFo+bsDxJdItemwC4
    42  KJ7L1iz39hRN/ZylMRLz5uTYRGddCkeIHhiG2h7zohH/MaYzUacXEEy3AoGBANz8
    43  e/msleB+iXC0cXKwds26N4hyMdAFE5qAqJXvV3S2W8JZnmU+sS7vPAWMYPlERPk1
    44  D8Q2eXqdPIkAWBhrx4RxD7rNc5qFNcQWEhCIxC9fccluH1y5g2M+4jpMX2CT8Uv+
    45  3z+NoJ5uDTXZTnLCfoZzgZ4nCZVZ+6iU5U1+YXFJAoGBANLPpIV920n/nJmmquMj
    46  orI1R/QXR9Cy56cMC65agezlGOfTYxk5Cfl5Ve+/2IJCfgzwJyjWUsFx7RviEeGw
    47  64o7JoUom1HX+5xxdHPsyZ96OoTJ5RqtKKoApnhRMamau0fWydH1yeOEJd+TRHhc
    48  XStGfhz8QNa1dVFvENczja1vAoGABGWhsd4VPVpHMc7lUvrf4kgKQtTC2PjA4xoc
    49  QJ96hf/642sVE76jl+N6tkGMzGjnVm4P2j+bOy1VvwQavKGoXqJBRd5Apppv727g
    50  /SM7hBXKFc/zH80xKBBgP/i1DR7kdjakCoeu4ngeGywvu2jTS6mQsqzkK+yWbUxJ
    51  I7mYBsECgYB/KNXlTEpXtz/kwWCHFSYA8U74l7zZbVD8ul0e56JDK+lLcJ0tJffk
    52  gqnBycHj6AhEycjda75cs+0zybZvN4x65KZHOGW/O/7OAWEcZP5TPb3zf9ned3Hl
    53  NsZoFj52ponUM6+99A2CmezFCN16c4mbA//luWF+k3VVqR6BpkrhKw==
    54  -----END RSA PRIVATE KEY-----`
    55  
    56  // this cert was signed by the key from testCAPublicKey
    57  const testServerHostCert = `ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgvQ3Bs1ex7277b9q6I0fNaWsVEC16f+LcT8RLPSVMEVMAAAADAQABAAABAQDX2UZWxOohPmKI1hGCehjULCRsRNblyr5HOTm/+ROV/fVelJTvQdVaRtMREQKNph1czaAZxtv6zGmroa1d/UzeRWibJyqHHCE+/gKvpenhZP+OQXH3P4UXOl6h0YlaM4fovYfm5fUK+v0QN1Cn2338nfb+oEWe1jwbChQj/L/UxJOYyIW26l0w4M3Tri93eDIwpPCuVDy1kzppi7I4+y60uVRjsznHkXAwNi+c8NJ7JP8jDTOzcH40LKp54x3ZPtjNAWdEBOPQzuszkuhKzsNWpWuI4QAGywXIuPfU9uhqguE4qByqgz2SGQ3OvsUdW+L4OFgzaMPQPC+pks3o2acvAAAAAAAAAAAAAAACAAAAB2NhLXRlc3QAAAANAAAACTEyNy4wLjAuMQAAAABag0jkAAAAAHDcHtAAAAAAAAAAAAAAAAAAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQCrozyZIhdEvalCn+eSzHH94cO9ykiywA13ntWI7mJcHBwYTeCYWG8E9zGXyp2iDOjCGudM0Tdt8o0OofKChk9Z/qiUN0G8y1kmaXBlBM3qA5R9NPpvMYMNkYLfX6ivtZCnqrsbzaoqN2Oc/7H2StHzJWh/XCGu9otQZA6vdv1oSmAsZOjw/xIGaGQqDUaLq21J280PP1qSbdJHf76iSHE+TWe3YpqV946JWM5tCh0DykZ10VznvxYpUjzhr07IN3tVKxOXbPnnU7lX6IaLIWgfzLqwSyheeux05c3JLF9iF4sFu8ou4hwQz1iuUTU1jxgwZP0w/bkXgFFs0949lW81AAABDwAAAAdzc2gtcnNhAAABAEyoiVkZ5z79nh3WSU5mU2U7e2BItnnEqsJIm9EN+35uG0yORSXmQoaa9mtli7G3r79tyqEJd/C95EdNvU/9TjaoDcbH8OHP+Ue9XSfUzBuQ6bGSXe6mlZlO7QJ1cIyWphFP3MkrweDSiJ+SpeXzLzZkiJ7zKv5czhBEyG/MujFgvikotL+eUNG42y2cgsesXSjENSBS3l11q55a+RM2QKt3W32im8CsSxrH6Mz6p4JXQNgsVvZRknLxNlWXULFB2HLTunPKzJNMTf6xZf66oivSBAXVIdNKhlVpAQ3dT/dW5K6J4aQF/hjWByyLprFwZ16cPDqvtalnTCpbRYelNbw=`
    58  
    59  const testCAPublicKey = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCrozyZIhdEvalCn+eSzHH94cO9ykiywA13ntWI7mJcHBwYTeCYWG8E9zGXyp2iDOjCGudM0Tdt8o0OofKChk9Z/qiUN0G8y1kmaXBlBM3qA5R9NPpvMYMNkYLfX6ivtZCnqrsbzaoqN2Oc/7H2StHzJWh/XCGu9otQZA6vdv1oSmAsZOjw/xIGaGQqDUaLq21J280PP1qSbdJHf76iSHE+TWe3YpqV946JWM5tCh0DykZ10VznvxYpUjzhr07IN3tVKxOXbPnnU7lX6IaLIWgfzLqwSyheeux05c3JLF9iF4sFu8ou4hwQz1iuUTU1jxgwZP0w/bkXgFFs0949lW81`
    60  
    61  func newMockLineServer(t *testing.T, signer ssh.Signer, pubKey string) string {
    62  	serverConfig := &ssh.ServerConfig{
    63  		PasswordCallback:  acceptUserPass("user", "pass"),
    64  		PublicKeyCallback: acceptPublicKey(pubKey),
    65  	}
    66  
    67  	var err error
    68  	if signer == nil {
    69  		signer, err = ssh.ParsePrivateKey([]byte(testServerPrivateKey))
    70  		if err != nil {
    71  			t.Fatalf("unable to parse private key: %s", err)
    72  		}
    73  	}
    74  	serverConfig.AddHostKey(signer)
    75  
    76  	l, err := net.Listen("tcp", "127.0.0.1:0")
    77  	if err != nil {
    78  		t.Fatalf("Unable to listen for connection: %s", err)
    79  	}
    80  
    81  	go func() {
    82  		defer l.Close()
    83  		c, err := l.Accept()
    84  		if err != nil {
    85  			t.Errorf("Unable to accept incoming connection: %s", err)
    86  		}
    87  		defer c.Close()
    88  		conn, chans, _, err := ssh.NewServerConn(c, serverConfig)
    89  		if err != nil {
    90  			t.Logf("Handshaking error: %v", err)
    91  		}
    92  		t.Log("Accepted SSH connection")
    93  
    94  		for newChannel := range chans {
    95  			channel, requests, err := newChannel.Accept()
    96  			if err != nil {
    97  				t.Errorf("Unable to accept channel.")
    98  			}
    99  			t.Log("Accepted channel")
   100  
   101  			go func(in <-chan *ssh.Request) {
   102  				defer channel.Close()
   103  				for req := range in {
   104  					// since this channel's requests are serviced serially,
   105  					// this will block keepalive probes, and can simulate a
   106  					// hung connection.
   107  					if bytes.Contains(req.Payload, []byte("sleep")) {
   108  						time.Sleep(time.Second)
   109  					}
   110  
   111  					if req.WantReply {
   112  						req.Reply(true, nil)
   113  					}
   114  				}
   115  			}(requests)
   116  		}
   117  		conn.Close()
   118  	}()
   119  
   120  	return l.Addr().String()
   121  }
   122  
   123  func TestNew_Invalid(t *testing.T) {
   124  	address := newMockLineServer(t, nil, testClientPublicKey)
   125  	parts := strings.Split(address, ":")
   126  
   127  	v := cty.ObjectVal(map[string]cty.Value{
   128  		"type":     cty.StringVal("ssh"),
   129  		"user":     cty.StringVal("user"),
   130  		"password": cty.StringVal("i-am-invalid"),
   131  		"host":     cty.StringVal(parts[0]),
   132  		"port":     cty.StringVal(parts[1]),
   133  		"timeout":  cty.StringVal("30s"),
   134  	})
   135  
   136  	c, err := New(v)
   137  	if err != nil {
   138  		t.Fatalf("error creating communicator: %s", err)
   139  	}
   140  
   141  	err = c.Connect(nil)
   142  	if err == nil {
   143  		t.Fatal("should have had an error connecting")
   144  	}
   145  }
   146  
   147  func TestNew_InvalidHost(t *testing.T) {
   148  	v := cty.ObjectVal(map[string]cty.Value{
   149  		"type":     cty.StringVal("ssh"),
   150  		"user":     cty.StringVal("user"),
   151  		"password": cty.StringVal("i-am-invalid"),
   152  		"port":     cty.StringVal("22"),
   153  		"timeout":  cty.StringVal("30s"),
   154  	})
   155  
   156  	_, err := New(v)
   157  	if err == nil {
   158  		t.Fatal("should have had an error creating communicator")
   159  	}
   160  }
   161  
   162  func TestStart(t *testing.T) {
   163  	address := newMockLineServer(t, nil, testClientPublicKey)
   164  	parts := strings.Split(address, ":")
   165  
   166  	v := cty.ObjectVal(map[string]cty.Value{
   167  		"type":     cty.StringVal("ssh"),
   168  		"user":     cty.StringVal("user"),
   169  		"password": cty.StringVal("pass"),
   170  		"host":     cty.StringVal(parts[0]),
   171  		"port":     cty.StringVal(parts[1]),
   172  		"timeout":  cty.StringVal("30s"),
   173  	})
   174  
   175  	c, err := New(v)
   176  	if err != nil {
   177  		t.Fatalf("error creating communicator: %s", err)
   178  	}
   179  
   180  	var cmd remote.Cmd
   181  	stdout := new(bytes.Buffer)
   182  	cmd.Command = "echo foo"
   183  	cmd.Stdout = stdout
   184  
   185  	err = c.Start(&cmd)
   186  	if err != nil {
   187  		t.Fatalf("error executing remote command: %s", err)
   188  	}
   189  }
   190  
   191  // TestKeepAlives verifies that the keepalive messages don't interfere with
   192  // normal operation of the client.
   193  func TestKeepAlives(t *testing.T) {
   194  	ivl := keepAliveInterval
   195  	keepAliveInterval = 250 * time.Millisecond
   196  	defer func() { keepAliveInterval = ivl }()
   197  
   198  	address := newMockLineServer(t, nil, testClientPublicKey)
   199  	parts := strings.Split(address, ":")
   200  
   201  	v := cty.ObjectVal(map[string]cty.Value{
   202  		"type":     cty.StringVal("ssh"),
   203  		"user":     cty.StringVal("user"),
   204  		"password": cty.StringVal("pass"),
   205  		"host":     cty.StringVal(parts[0]),
   206  		"port":     cty.StringVal(parts[1]),
   207  	})
   208  
   209  	c, err := New(v)
   210  	if err != nil {
   211  		t.Fatalf("error creating communicator: %s", err)
   212  	}
   213  
   214  	if err := c.Connect(nil); err != nil {
   215  		t.Fatal(err)
   216  	}
   217  
   218  	var cmd remote.Cmd
   219  	stdout := new(bytes.Buffer)
   220  	cmd.Command = "sleep"
   221  	cmd.Stdout = stdout
   222  
   223  	// wait a bit before executing the command, so that at least 1 keepalive is sent
   224  	time.Sleep(500 * time.Millisecond)
   225  
   226  	err = c.Start(&cmd)
   227  	if err != nil {
   228  		t.Fatalf("error executing remote command: %s", err)
   229  	}
   230  }
   231  
   232  // TestDeadConnection verifies that failed keepalive messages will eventually
   233  // kill the connection.
   234  func TestFailedKeepAlives(t *testing.T) {
   235  	ivl := keepAliveInterval
   236  	del := maxKeepAliveDelay
   237  	maxKeepAliveDelay = 500 * time.Millisecond
   238  	keepAliveInterval = 250 * time.Millisecond
   239  	defer func() {
   240  		keepAliveInterval = ivl
   241  		maxKeepAliveDelay = del
   242  	}()
   243  
   244  	address := newMockLineServer(t, nil, testClientPublicKey)
   245  	parts := strings.Split(address, ":")
   246  
   247  	v := cty.ObjectVal(map[string]cty.Value{
   248  		"type":     cty.StringVal("ssh"),
   249  		"user":     cty.StringVal("user"),
   250  		"password": cty.StringVal("pass"),
   251  		"host":     cty.StringVal(parts[0]),
   252  		"port":     cty.StringVal(parts[1]),
   253  		"timeout":  cty.StringVal("30s"),
   254  	})
   255  
   256  	c, err := New(v)
   257  	if err != nil {
   258  		t.Fatalf("error creating communicator: %s", err)
   259  	}
   260  
   261  	if err := c.Connect(nil); err != nil {
   262  		t.Fatal(err)
   263  	}
   264  	var cmd remote.Cmd
   265  	stdout := new(bytes.Buffer)
   266  	cmd.Command = "sleep"
   267  	cmd.Stdout = stdout
   268  
   269  	err = c.Start(&cmd)
   270  	if err == nil {
   271  		t.Fatal("expected connection error")
   272  	}
   273  }
   274  
   275  func TestLostConnection(t *testing.T) {
   276  	address := newMockLineServer(t, nil, testClientPublicKey)
   277  	parts := strings.Split(address, ":")
   278  
   279  	v := cty.ObjectVal(map[string]cty.Value{
   280  		"type":     cty.StringVal("ssh"),
   281  		"user":     cty.StringVal("user"),
   282  		"password": cty.StringVal("pass"),
   283  		"host":     cty.StringVal(parts[0]),
   284  		"port":     cty.StringVal(parts[1]),
   285  		"timeout":  cty.StringVal("30s"),
   286  	})
   287  
   288  	c, err := New(v)
   289  	if err != nil {
   290  		t.Fatalf("error creating communicator: %s", err)
   291  	}
   292  
   293  	var cmd remote.Cmd
   294  	stdout := new(bytes.Buffer)
   295  	cmd.Command = "echo foo"
   296  	cmd.Stdout = stdout
   297  
   298  	err = c.Start(&cmd)
   299  	if err != nil {
   300  		t.Fatalf("error executing remote command: %s", err)
   301  	}
   302  
   303  	// The test server can't execute anything, so Wait will block, unless
   304  	// there's an error.  Disconnect the communicator transport, to cause the
   305  	// command to fail.
   306  	go func() {
   307  		time.Sleep(100 * time.Millisecond)
   308  		c.Disconnect()
   309  	}()
   310  
   311  	err = cmd.Wait()
   312  	if err == nil {
   313  		t.Fatal("expected communicator error")
   314  	}
   315  }
   316  
   317  func TestHostKey(t *testing.T) {
   318  	// get the server's public key
   319  	signer, err := ssh.ParsePrivateKey([]byte(testServerPrivateKey))
   320  	if err != nil {
   321  		t.Fatalf("unable to parse private key: %v", err)
   322  	}
   323  	pubKey := fmt.Sprintf("ssh-rsa %s", base64.StdEncoding.EncodeToString(signer.PublicKey().Marshal()))
   324  
   325  	address := newMockLineServer(t, nil, testClientPublicKey)
   326  	host, p, _ := net.SplitHostPort(address)
   327  	port, _ := strconv.Atoi(p)
   328  
   329  	connInfo := &connectionInfo{
   330  		User:     "user",
   331  		Password: "pass",
   332  		Host:     host,
   333  		HostKey:  pubKey,
   334  		Port:     uint16(port),
   335  		Timeout:  "30s",
   336  	}
   337  
   338  	cfg, err := prepareSSHConfig(connInfo)
   339  	if err != nil {
   340  		t.Fatal(err)
   341  	}
   342  
   343  	c := &Communicator{
   344  		connInfo: connInfo,
   345  		config:   cfg,
   346  	}
   347  
   348  	var cmd remote.Cmd
   349  	stdout := new(bytes.Buffer)
   350  	cmd.Command = "echo foo"
   351  	cmd.Stdout = stdout
   352  
   353  	if err := c.Start(&cmd); err != nil {
   354  		t.Fatal(err)
   355  	}
   356  	if err := c.Disconnect(); err != nil {
   357  		t.Fatal(err)
   358  	}
   359  
   360  	// now check with the wrong HostKey
   361  	address = newMockLineServer(t, nil, testClientPublicKey)
   362  	_, p, _ = net.SplitHostPort(address)
   363  	port, _ = strconv.Atoi(p)
   364  
   365  	connInfo.HostKey = testClientPublicKey
   366  	connInfo.Port = uint16(port)
   367  
   368  	cfg, err = prepareSSHConfig(connInfo)
   369  	if err != nil {
   370  		t.Fatal(err)
   371  	}
   372  
   373  	c = &Communicator{
   374  		connInfo: connInfo,
   375  		config:   cfg,
   376  	}
   377  
   378  	err = c.Start(&cmd)
   379  	if err == nil || !strings.Contains(err.Error(), "mismatch") {
   380  		t.Fatalf("expected host key mismatch, got error:%v", err)
   381  	}
   382  }
   383  
   384  func TestHostCert(t *testing.T) {
   385  	pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(testServerHostCert))
   386  	if err != nil {
   387  		t.Fatal(err)
   388  	}
   389  
   390  	signer, err := ssh.ParsePrivateKey([]byte(testServerPrivateKey))
   391  	if err != nil {
   392  		t.Fatal(err)
   393  	}
   394  
   395  	signer, err = ssh.NewCertSigner(pk.(*ssh.Certificate), signer)
   396  	if err != nil {
   397  		t.Fatal(err)
   398  	}
   399  
   400  	address := newMockLineServer(t, signer, testClientPublicKey)
   401  	host, p, _ := net.SplitHostPort(address)
   402  	port, _ := strconv.Atoi(p)
   403  
   404  	connInfo := &connectionInfo{
   405  		User:     "user",
   406  		Password: "pass",
   407  		Host:     host,
   408  		HostKey:  testCAPublicKey,
   409  		Port:     uint16(port),
   410  		Timeout:  "30s",
   411  	}
   412  
   413  	cfg, err := prepareSSHConfig(connInfo)
   414  	if err != nil {
   415  		t.Fatal(err)
   416  	}
   417  
   418  	c := &Communicator{
   419  		connInfo: connInfo,
   420  		config:   cfg,
   421  	}
   422  
   423  	var cmd remote.Cmd
   424  	stdout := new(bytes.Buffer)
   425  	cmd.Command = "echo foo"
   426  	cmd.Stdout = stdout
   427  
   428  	if err := c.Start(&cmd); err != nil {
   429  		t.Fatal(err)
   430  	}
   431  	if err := c.Disconnect(); err != nil {
   432  		t.Fatal(err)
   433  	}
   434  
   435  	// now check with the wrong HostKey
   436  	address = newMockLineServer(t, signer, testClientPublicKey)
   437  	_, p, _ = net.SplitHostPort(address)
   438  	port, _ = strconv.Atoi(p)
   439  
   440  	connInfo.HostKey = testClientPublicKey
   441  	connInfo.Port = uint16(port)
   442  
   443  	cfg, err = prepareSSHConfig(connInfo)
   444  	if err != nil {
   445  		t.Fatal(err)
   446  	}
   447  
   448  	c = &Communicator{
   449  		connInfo: connInfo,
   450  		config:   cfg,
   451  	}
   452  
   453  	err = c.Start(&cmd)
   454  	if err == nil || !strings.Contains(err.Error(), "authorities") {
   455  		t.Fatalf("expected host key mismatch, got error:%v", err)
   456  	}
   457  }
   458  
   459  const SERVER_PEM = `-----BEGIN RSA PRIVATE KEY-----
   460  MIIEpAIBAAKCAQEA8CkDr7uxCFt6lQUVwS8NyPO+fQNxORoGnMnN/XhVJZvpqyKR
   461  Uji9R0d8D66bYxUUsabXjP2y4HTVzbZtnvXFZZshk0cOtJjjekpYJaLK2esPR/iX
   462  wvSltNkrDQDPN/RmgEEMIevW8AgrPsqrnybFHxTpd7rEUHXBOe4nMNRIg3XHykB6
   463  jZk8q5bBPUe3I/f0DK5TJEBpTc6dO3P/j93u55VUqr39/SPRHnld2mCw+c8v6UOh
   464  sssO/DIZFPScD3DYqsk2N+/nz9zXfcOTdWGhawgxuIo1DTokrNQbG3pDrLqcWgqj
   465  13vqJFCmRA0O2CQIwJePd6+Np/XO3Uh/KL6FlQIDAQABAoIBAQCmvQMXNmvCDqk7
   466  30zsVDvw4fHGH+azK3Od1aqTqcEMHISOUbCtckFPxLzIsoSltRQqB1kuRVG07skm
   467  Stsu+xny4lLcSwBVuLRuykEK2EyYIc/5Owo6y9pkhkaSf5ZfFes4bnD6+B/BhRpp
   468  PRMMq0E+xCkX/G6iIi9mhgdlqm0x/vKtjzQeeshw9+gRcRLUpX+UeKFKXMXcDayx
   469  qekr1bAaQKNBhTK+CbZjcqzG4f+BXVGRTZ9nsPAV+yTnWUCU0TghwPmtthHbebqa
   470  9hlkum7qik/bQj/tjJ8/b0vTfHQSVxhtPG/ZV2Tn9ZuL/vrkYqeyMU8XkJ/uaEvH
   471  WPyOcB4BAoGBAP5o5JSEtPog+U3JFrLNSRjz5ofZNVkJzice+0XyqlzJDHhX5tF8
   472  mriYQZLLXYhckBm4IdkhTn/dVbXNQTzyy2WVuO5nU8bkCMvGL9CGpW4YGqwGf7NX
   473  e4H3emtRjLv8VZpUHe/RUUDhmYvMSt1qmXuskfpROuGfLhQBUd6A4J+BAoGBAPGp
   474  UcMKjrxZ5qjYU6DLgS+xeca4Eu70HgdbSQbRo45WubXjyXvTRFij36DrpxJWf1D7
   475  lIsyBifoTra/lAuC1NQXGYWjTCdk2ey8Ll5qOgiXvE6lINHABr+U/Z90/g6LuML2
   476  VzaZbq/QLcT3yVsdyTogKckzCaKsCpusyHE1CXAVAoGAd6kMglKc8N0bhZukgnsN
   477  +5+UeacPcY6sGTh4RWErAjNKGzx1A2lROKvcg9gFaULoQECcIw2IZ5nKW5VsLueg
   478  BWrTrcaJ4A2XmYjhKnp6SvspaGoyHD90hx/Iw7t6r1yzQsB3yDmytwqldtyjBdvC
   479  zynPC2azhDWjraMlR7tka4ECgYAxwvLiHa9sm3qCtCDsUFtmrb3srITBjaUNUL/F
   480  1q8+JR+Sk7gudj9xnTT0VvINNaB71YIt83wPBagHu4VJpYQbtDH+MbUBu6OgOtO1
   481  f1w53rzY2OncJxV8p7pd9mJGLoE6LC2jQY7oRw7Vq0xcJdME1BCmrIrEY3a/vaF8
   482  pjYuTQKBgQCIOH23Xita8KmhH0NdlWxZfcQt1j3AnOcKe6UyN4BsF8hqS7eTA52s
   483  WjG5X2IBl7gs1eMM1qkqR8npS9nwfO/pBmZPwjiZoilypXxWj+c+P3vwre2yija4
   484  bXgFVj4KFBwhr1+8KcobxC0SAPEouMvSkxzjjw+gnebozUtPlud9jA==
   485  -----END RSA PRIVATE KEY-----
   486  `
   487  const CLIENT_CERT_SIGNED_BY_SERVER = `ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgbMDNUn4M2TtzrSH7MOT2QsvLzZWjehJ5TYrBOp9p+lwAAAADAQABAAABAQCyu57E7zIWRyEWuaiOiikOSZKFjbwLkpE9fboFfLLsNUJj4zw+5bZUJtzWK8roPjgL8s1oPncro5wuTtI2Nu4fkpeFK0Hb33o6Eyksuj4Om4+6Uemn1QEcb0bZqK8Zyg9Dg9deP7LeE0v78b5/jZafFgwxv+/sMhM0PRD34NCDYcYmkkHlvQtQWFAdbPXCgghObedZyYdoqZVuhTsiPMWtQS/cc9M4tv6mPOuQlhZt3R/Oh/kwUyu45oGRb5bhO4JicozFS3oeClpU+UMbgslkzApJqxZBWN7+PDFSZhKk2GslyeyP4sH3E30Z00yVi/lQYgmQsB+Hg6ClemNQMNu/AAAAAAAAAAAAAAACAAAABHVzZXIAAAAIAAAABHVzZXIAAAAAWzBjXAAAAAB/POfPAAAAAAAAAAAAAAAAAAABFwAAAAdzc2gtcnNhAAAAAwEAAQAAAQEA8CkDr7uxCFt6lQUVwS8NyPO+fQNxORoGnMnN/XhVJZvpqyKRUji9R0d8D66bYxUUsabXjP2y4HTVzbZtnvXFZZshk0cOtJjjekpYJaLK2esPR/iXwvSltNkrDQDPN/RmgEEMIevW8AgrPsqrnybFHxTpd7rEUHXBOe4nMNRIg3XHykB6jZk8q5bBPUe3I/f0DK5TJEBpTc6dO3P/j93u55VUqr39/SPRHnld2mCw+c8v6UOhsssO/DIZFPScD3DYqsk2N+/nz9zXfcOTdWGhawgxuIo1DTokrNQbG3pDrLqcWgqj13vqJFCmRA0O2CQIwJePd6+Np/XO3Uh/KL6FlQAAAQ8AAAAHc3NoLXJzYQAAAQC6sKEQHyl954BQn2BXuTgOB3NkENBxN7SD8ZaS8PNkDESytLjSIqrzoE6m7xuzprA+G23XRrCY/um3UvM7+7+zbwig2NIBbGbp3QFliQHegQKW6hTZP09jAQZk5jRrrEr/QT/s+gtHPmjxJK7XOQYxhInDKj+aJg62ExcwpQlP/0ATKNOIkdzTzzq916p0UOnnVaaPMKibh5Lv69GafIhKJRZSuuLN9fvs1G1RuUbxn/BNSeoRCr54L++Ztg09fJxunoyELs8mwgzCgB3pdZoUR2Z6ak05W4mvH3lkSz2BKUrlwxI6mterxhJy1GuN1K/zBG0gEMl2UTLajGK3qKM8 itbitloaner@MacBook-Pro-4.fios-router.home`
   488  const CLIENT_PEM = `-----BEGIN RSA PRIVATE KEY-----
   489  MIIEpAIBAAKCAQEAsruexO8yFkchFrmojoopDkmShY28C5KRPX26BXyy7DVCY+M8
   490  PuW2VCbc1ivK6D44C/LNaD53K6OcLk7SNjbuH5KXhStB2996OhMpLLo+DpuPulHp
   491  p9UBHG9G2aivGcoPQ4PXXj+y3hNL+/G+f42WnxYMMb/v7DITND0Q9+DQg2HGJpJB
   492  5b0LUFhQHWz1woIITm3nWcmHaKmVboU7IjzFrUEv3HPTOLb+pjzrkJYWbd0fzof5
   493  MFMruOaBkW+W4TuCYnKMxUt6HgpaVPlDG4LJZMwKSasWQVje/jwxUmYSpNhrJcns
   494  j+LB9xN9GdNMlYv5UGIJkLAfh4OgpXpjUDDbvwIDAQABAoIBAEu2ctFVyk/pnbi0
   495  uRR4rl+hBvKQUeJNGj2ELvL4Ggs5nIAX2IOEZ7JKLC6FqpSrFq7pEd5g57aSvixX
   496  s3DH4CN7w7fj1ShBCNPlHgIWewdRGpeA74vrDWdwNAEsFdDE6aZeCTOhpDGy1vNJ
   497  OrtpzS5i9pN0jTvvEneEjtWSZIHiiVlN+0hsFaiwZ6KXON+sDccZPmnP6Fzwj5Rc
   498  WS0dKSwnxnx0otWgwWFs8nr306nSeMsNmQkHsS9lz4DEVpp9owdzrX1JmbQvNYAV
   499  ohmB3ET4JYFgerqPXJfed9poueGuWCP6MYhsjNeHN35QhofxdO5/0i3JlZfqwZei
   500  tNq/0oECgYEA6SqjRqDiIp3ajwyB7Wf0cIQG/P6JZDyN1jl//htgniliIH5UP1Tm
   501  uAMG5MincV6X9lOyXyh6Yofu5+NR0yt9SqbDZVJ3ZCxKTun7pxJvQFd7wl5bMkiJ
   502  qVfS08k6gQHHDoO+eel+DtpIfWc+e3tvX0aihSU0GZEMqDXYkkphLGECgYEAxDxb
   503  +JwJ3N5UEjjkuvFBpuJnmjIaN9HvQkTv3inlx1gLE4iWBZXXsu4aWF8MCUeAAZyP
   504  42hQDSkCYX/A22tYCEn/jfrU6A+6rkWBTjdUlYLvlSkhosSnO+117WEItb5cUE95
   505  hF4UY7LNs1AsDkV4WE87f/EjpxSwUAjB2Lfd/B8CgYAJ/JiHsuZcozQ0Qk3iVDyF
   506  ATKnbWOHFozgqw/PW27U92LLj32eRM2o/gAylmGNmoaZt1YBe2NaiwXxiqv7hnZU
   507  VzYxRcn1UWxRWvY7Xq/DKrwTRCVVzwOObEOMbKcD1YaoGX50DEso6bKHJH/pnAzW
   508  INlfKIvFuI+5OK0w/tyQoQKBgQCf/jpaOxaLfrV62eobRQJrByLDBGB97GsvU7di
   509  IjTWz8DQH0d5rE7d8uWF8ZCFrEcAiV6DYZQK9smbJqbd/uoacAKtBro5rkFdPwwK
   510  8m/DKqsdqRhkdgOHh7bjYH7Sdy8ax4Fi27WyB6FQtmgFBrz0+zyetsODwQlzZ4Bs
   511  qpSRrwKBgQC0vWHrY5aGIdF+b8EpP0/SSLLALpMySHyWhDyxYcPqdhszYbjDcavv
   512  xrrLXNUD2duBHKPVYE+7uVoDkpZXLUQ4x8argo/IwQM6Kh2ma1y83TYMT6XhL1+B
   513  5UPcl6RXZBCkiU7nFIG6/0XKFqVWc3fU8e09X+iJwXIJ5Jatywtg+g==
   514  -----END RSA PRIVATE KEY-----
   515  `
   516  
   517  func TestCertificateBasedAuth(t *testing.T) {
   518  	signer, err := ssh.ParsePrivateKey([]byte(SERVER_PEM))
   519  	if err != nil {
   520  		t.Fatalf("unable to parse private key: %v", err)
   521  	}
   522  	address := newMockLineServer(t, signer, CLIENT_CERT_SIGNED_BY_SERVER)
   523  	host, p, _ := net.SplitHostPort(address)
   524  	port, _ := strconv.Atoi(p)
   525  
   526  	connInfo := &connectionInfo{
   527  		User:        "user",
   528  		Host:        host,
   529  		PrivateKey:  CLIENT_PEM,
   530  		Certificate: CLIENT_CERT_SIGNED_BY_SERVER,
   531  		Port:        uint16(port),
   532  		Timeout:     "30s",
   533  	}
   534  
   535  	cfg, err := prepareSSHConfig(connInfo)
   536  	if err != nil {
   537  		t.Fatal(err)
   538  	}
   539  
   540  	c := &Communicator{
   541  		connInfo: connInfo,
   542  		config:   cfg,
   543  	}
   544  
   545  	var cmd remote.Cmd
   546  	stdout := new(bytes.Buffer)
   547  	cmd.Command = "echo foo"
   548  	cmd.Stdout = stdout
   549  
   550  	if err := c.Start(&cmd); err != nil {
   551  		t.Fatal(err)
   552  	}
   553  	if err := c.Disconnect(); err != nil {
   554  		t.Fatal(err)
   555  	}
   556  }
   557  
   558  func TestAccUploadFile(t *testing.T) {
   559  	// use the local ssh server and scp binary to check uploads
   560  	if ok := os.Getenv("SSH_UPLOAD_TEST"); ok == "" {
   561  		t.Log("Skipping Upload Acceptance without SSH_UPLOAD_TEST set")
   562  		t.Skip()
   563  	}
   564  
   565  	v := cty.ObjectVal(map[string]cty.Value{
   566  		"type":    cty.StringVal("ssh"),
   567  		"user":    cty.StringVal(os.Getenv("USER")),
   568  		"host":    cty.StringVal("127.0.0.1"),
   569  		"port":    cty.StringVal("22"),
   570  		"timeout": cty.StringVal("30s"),
   571  	})
   572  
   573  	c, err := New(v)
   574  	if err != nil {
   575  		t.Fatalf("error creating communicator: %s", err)
   576  	}
   577  
   578  	tmpDir, err := ioutil.TempDir("", "communicator")
   579  	if err != nil {
   580  		t.Fatal(err)
   581  	}
   582  	defer os.RemoveAll(tmpDir)
   583  
   584  	content := []byte("this is the file content")
   585  	source := bytes.NewReader(content)
   586  	tmpFile := filepath.Join(tmpDir, "tempFile.out")
   587  	err = c.Upload(tmpFile, source)
   588  	if err != nil {
   589  		t.Fatalf("error uploading file: %s", err)
   590  	}
   591  
   592  	data, err := ioutil.ReadFile(tmpFile)
   593  	if err != nil {
   594  		t.Fatal(err)
   595  	}
   596  
   597  	if !bytes.Equal(data, content) {
   598  		t.Fatalf("bad: %s", data)
   599  	}
   600  }
   601  
   602  func TestAccHugeUploadFile(t *testing.T) {
   603  	// use the local ssh server and scp binary to check uploads
   604  	if ok := os.Getenv("SSH_UPLOAD_TEST"); ok == "" {
   605  		t.Log("Skipping Upload Acceptance without SSH_UPLOAD_TEST set")
   606  		t.Skip()
   607  	}
   608  
   609  	v := cty.ObjectVal(map[string]cty.Value{
   610  		"type":    cty.StringVal("ssh"),
   611  		"host":    cty.StringVal("127.0.0.1"),
   612  		"user":    cty.StringVal(os.Getenv("USER")),
   613  		"port":    cty.StringVal("22"),
   614  		"timeout": cty.StringVal("30s"),
   615  	})
   616  
   617  	c, err := New(v)
   618  	if err != nil {
   619  		t.Fatalf("error creating communicator: %s", err)
   620  	}
   621  
   622  	// copy 4GB of data, random to prevent compression.
   623  	size := int64(1 << 32)
   624  	source := io.LimitReader(rand.New(rand.NewSource(0)), size)
   625  
   626  	dest, err := ioutil.TempFile("", "communicator")
   627  	if err != nil {
   628  		t.Fatal(err)
   629  	}
   630  	destName := dest.Name()
   631  	dest.Close()
   632  	defer os.Remove(destName)
   633  
   634  	t.Log("Uploading to", destName)
   635  
   636  	// bypass the Upload method so we can directly supply the file size
   637  	// preventing the extra copy of the huge file.
   638  	targetDir := filepath.Dir(destName)
   639  	targetFile := filepath.Base(destName)
   640  
   641  	scpFunc := func(w io.Writer, stdoutR *bufio.Reader) error {
   642  		return scpUploadFile(targetFile, source, w, stdoutR, size)
   643  	}
   644  
   645  	err = c.scpSession("scp -vt "+targetDir, scpFunc)
   646  	if err != nil {
   647  		t.Fatal(err)
   648  	}
   649  
   650  	// check the final file size
   651  	fs, err := os.Stat(destName)
   652  	if err != nil {
   653  		t.Fatal(err)
   654  	}
   655  
   656  	if fs.Size() != size {
   657  		t.Fatalf("expected file size of %d, got %d", size, fs.Size())
   658  	}
   659  }
   660  
   661  func TestScriptPath(t *testing.T) {
   662  	cases := []struct {
   663  		Input   string
   664  		Pattern string
   665  	}{
   666  		{
   667  			"/tmp/script.sh",
   668  			`^/tmp/script\.sh$`,
   669  		},
   670  		{
   671  			"/tmp/script_%RAND%.sh",
   672  			`^/tmp/script_(\d+)\.sh$`,
   673  		},
   674  	}
   675  
   676  	for _, tc := range cases {
   677  		v := cty.ObjectVal(map[string]cty.Value{
   678  			"type":        cty.StringVal("ssh"),
   679  			"host":        cty.StringVal("127.0.0.1"),
   680  			"script_path": cty.StringVal(tc.Input),
   681  		})
   682  
   683  		comm, err := New(v)
   684  		if err != nil {
   685  			t.Fatalf("err: %s", err)
   686  		}
   687  		output := comm.ScriptPath()
   688  
   689  		match, err := regexp.Match(tc.Pattern, []byte(output))
   690  		if err != nil {
   691  			t.Fatalf("bad: %s\n\nerr: %s", tc.Input, err)
   692  		}
   693  		if !match {
   694  			t.Fatalf("bad: %s\n\n%s", tc.Input, output)
   695  		}
   696  	}
   697  }
   698  
   699  func TestScriptPath_randSeed(t *testing.T) {
   700  	// Pre GH-4186 fix, this value was the deterministic start the pseudorandom
   701  	// chain of unseeded math/rand values for Int31().
   702  	staticSeedPath := "/tmp/terraform_1298498081.sh"
   703  	c, err := New(cty.ObjectVal(map[string]cty.Value{
   704  		"type": cty.StringVal("ssh"),
   705  		"host": cty.StringVal("127.0.0.1"),
   706  	}))
   707  	if err != nil {
   708  		t.Fatalf("err: %s", err)
   709  	}
   710  	path := c.ScriptPath()
   711  	if path == staticSeedPath {
   712  		t.Fatalf("rand not seeded! got: %s", path)
   713  	}
   714  }
   715  
   716  var testClientPublicKey = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDE6A1c4n+OtEPEFlNKTZf2i03L3NylSYmvmJ8OLmzLuPZmJBJt4G3VZ/60s1aKzwLKrTq20S+ONG4zvnK5zIPoauoNNdUJKbg944hB4OE+HDbrBhk7SH+YWCsCILBoSXwAVdUEic6FWf/SeqBSmTBySHvpuNOw16J+SK6Ardx8k64F2tRkZuC6AmOZijgKa/sQKjWAIVPk34ECM6OLfPc3kKUEfkdpYLvuMfuRMfSTlxn5lFC0b0SovK9aWfNMBH9iXLQkieQ5rXoyzUC7mwgnASgl8cqw1UrToiUuhvneduXBhbQfmC/Upv+tL6dSSk+0DlgVKEHuJmc8s8+/qpdL`
   717  
   718  func acceptUserPass(goodUser, goodPass string) func(ssh.ConnMetadata, []byte) (*ssh.Permissions, error) {
   719  	return func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
   720  		if c.User() == goodUser && string(pass) == goodPass {
   721  			return nil, nil
   722  		}
   723  		return nil, fmt.Errorf("password rejected for %q", c.User())
   724  	}
   725  }
   726  
   727  func acceptPublicKey(keystr string) func(ssh.ConnMetadata, ssh.PublicKey) (*ssh.Permissions, error) {
   728  	return func(_ ssh.ConnMetadata, inkey ssh.PublicKey) (*ssh.Permissions, error) {
   729  		goodkey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(keystr))
   730  		if err != nil {
   731  			return nil, fmt.Errorf("error parsing key: %v", err)
   732  		}
   733  
   734  		if bytes.Equal(inkey.Marshal(), goodkey.Marshal()) {
   735  			return nil, nil
   736  		}
   737  
   738  		return nil, fmt.Errorf("public key rejected")
   739  	}
   740  }