github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/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/hashicorp/terraform/communicator/remote"
    23  	"github.com/hashicorp/terraform/terraform"
    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  				for req := range in {
   103  					// since this channel's requests are serviced serially,
   104  					// this will block keepalive probes, and can simulate a
   105  					// hung connection.
   106  					if bytes.Contains(req.Payload, []byte("sleep")) {
   107  						time.Sleep(time.Second)
   108  					}
   109  
   110  					if req.WantReply {
   111  						req.Reply(true, nil)
   112  					}
   113  				}
   114  			}(requests)
   115  
   116  			defer channel.Close()
   117  		}
   118  		conn.Close()
   119  	}()
   120  
   121  	return l.Addr().String()
   122  }
   123  
   124  func TestNew_Invalid(t *testing.T) {
   125  	address := newMockLineServer(t, nil, testClientPublicKey)
   126  	parts := strings.Split(address, ":")
   127  
   128  	r := &terraform.InstanceState{
   129  		Ephemeral: terraform.EphemeralState{
   130  			ConnInfo: map[string]string{
   131  				"type":     "ssh",
   132  				"user":     "user",
   133  				"password": "i-am-invalid",
   134  				"host":     parts[0],
   135  				"port":     parts[1],
   136  				"timeout":  "30s",
   137  			},
   138  		},
   139  	}
   140  
   141  	c, err := New(r)
   142  	if err != nil {
   143  		t.Fatalf("error creating communicator: %s", err)
   144  	}
   145  
   146  	err = c.Connect(nil)
   147  	if err == nil {
   148  		t.Fatal("should have had an error connecting")
   149  	}
   150  }
   151  
   152  func TestStart(t *testing.T) {
   153  	address := newMockLineServer(t, nil, testClientPublicKey)
   154  	parts := strings.Split(address, ":")
   155  
   156  	r := &terraform.InstanceState{
   157  		Ephemeral: terraform.EphemeralState{
   158  			ConnInfo: map[string]string{
   159  				"type":     "ssh",
   160  				"user":     "user",
   161  				"password": "pass",
   162  				"host":     parts[0],
   163  				"port":     parts[1],
   164  				"timeout":  "30s",
   165  			},
   166  		},
   167  	}
   168  
   169  	c, err := New(r)
   170  	if err != nil {
   171  		t.Fatalf("error creating communicator: %s", err)
   172  	}
   173  
   174  	var cmd remote.Cmd
   175  	stdout := new(bytes.Buffer)
   176  	cmd.Command = "echo foo"
   177  	cmd.Stdout = stdout
   178  
   179  	err = c.Start(&cmd)
   180  	if err != nil {
   181  		t.Fatalf("error executing remote command: %s", err)
   182  	}
   183  }
   184  
   185  // TestKeepAlives verifies that the keepalive messages don't interfere with
   186  // normal operation of the client.
   187  func TestKeepAlives(t *testing.T) {
   188  	ivl := keepAliveInterval
   189  	keepAliveInterval = 250 * time.Millisecond
   190  	defer func() { keepAliveInterval = ivl }()
   191  
   192  	address := newMockLineServer(t, nil, testClientPublicKey)
   193  	parts := strings.Split(address, ":")
   194  
   195  	r := &terraform.InstanceState{
   196  		Ephemeral: terraform.EphemeralState{
   197  			ConnInfo: map[string]string{
   198  				"type":     "ssh",
   199  				"user":     "user",
   200  				"password": "pass",
   201  				"host":     parts[0],
   202  				"port":     parts[1],
   203  			},
   204  		},
   205  	}
   206  
   207  	c, err := New(r)
   208  	if err != nil {
   209  		t.Fatalf("error creating communicator: %s", err)
   210  	}
   211  
   212  	if err := c.Connect(nil); err != nil {
   213  		t.Fatal(err)
   214  	}
   215  
   216  	var cmd remote.Cmd
   217  	stdout := new(bytes.Buffer)
   218  	cmd.Command = "sleep"
   219  	cmd.Stdout = stdout
   220  
   221  	// wait a bit before executing the command, so that at least 1 keepalive is sent
   222  	time.Sleep(500 * time.Millisecond)
   223  
   224  	err = c.Start(&cmd)
   225  	if err != nil {
   226  		t.Fatalf("error executing remote command: %s", err)
   227  	}
   228  }
   229  
   230  // TestDeadConnection verifies that failed keepalive messages will eventually
   231  // kill the connection.
   232  func TestFailedKeepAlives(t *testing.T) {
   233  	ivl := keepAliveInterval
   234  	del := maxKeepAliveDelay
   235  	maxKeepAliveDelay = 500 * time.Millisecond
   236  	keepAliveInterval = 250 * time.Millisecond
   237  	defer func() {
   238  		keepAliveInterval = ivl
   239  		maxKeepAliveDelay = del
   240  	}()
   241  
   242  	address := newMockLineServer(t, nil, testClientPublicKey)
   243  	parts := strings.Split(address, ":")
   244  
   245  	r := &terraform.InstanceState{
   246  		Ephemeral: terraform.EphemeralState{
   247  			ConnInfo: map[string]string{
   248  				"type":     "ssh",
   249  				"user":     "user",
   250  				"password": "pass",
   251  				"host":     parts[0],
   252  				"port":     parts[1],
   253  			},
   254  		},
   255  	}
   256  
   257  	c, err := New(r)
   258  	if err != nil {
   259  		t.Fatalf("error creating communicator: %s", err)
   260  	}
   261  
   262  	if err := c.Connect(nil); err != nil {
   263  		t.Fatal(err)
   264  	}
   265  	var cmd remote.Cmd
   266  	stdout := new(bytes.Buffer)
   267  	cmd.Command = "sleep"
   268  	cmd.Stdout = stdout
   269  
   270  	err = c.Start(&cmd)
   271  	if err == nil {
   272  		t.Fatal("expected connection error")
   273  	}
   274  }
   275  
   276  func TestLostConnection(t *testing.T) {
   277  	address := newMockLineServer(t, nil, testClientPublicKey)
   278  	parts := strings.Split(address, ":")
   279  
   280  	r := &terraform.InstanceState{
   281  		Ephemeral: terraform.EphemeralState{
   282  			ConnInfo: map[string]string{
   283  				"type":     "ssh",
   284  				"user":     "user",
   285  				"password": "pass",
   286  				"host":     parts[0],
   287  				"port":     parts[1],
   288  				"timeout":  "30s",
   289  			},
   290  		},
   291  	}
   292  
   293  	c, err := New(r)
   294  	if err != nil {
   295  		t.Fatalf("error creating communicator: %s", err)
   296  	}
   297  
   298  	var cmd remote.Cmd
   299  	stdout := new(bytes.Buffer)
   300  	cmd.Command = "echo foo"
   301  	cmd.Stdout = stdout
   302  
   303  	err = c.Start(&cmd)
   304  	if err != nil {
   305  		t.Fatalf("error executing remote command: %s", err)
   306  	}
   307  
   308  	// The test server can't execute anything, so Wait will block, unless
   309  	// there's an error.  Disconnect the communicator transport, to cause the
   310  	// command to fail.
   311  	go func() {
   312  		time.Sleep(100 * time.Millisecond)
   313  		c.Disconnect()
   314  	}()
   315  
   316  	err = cmd.Wait()
   317  	if err == nil {
   318  		t.Fatal("expected communicator error")
   319  	}
   320  }
   321  
   322  func TestHostKey(t *testing.T) {
   323  	// get the server's public key
   324  	signer, err := ssh.ParsePrivateKey([]byte(testServerPrivateKey))
   325  	if err != nil {
   326  		t.Fatalf("unable to parse private key: %v", err)
   327  	}
   328  	pubKey := fmt.Sprintf("ssh-rsa %s", base64.StdEncoding.EncodeToString(signer.PublicKey().Marshal()))
   329  
   330  	address := newMockLineServer(t, nil, testClientPublicKey)
   331  	host, p, _ := net.SplitHostPort(address)
   332  	port, _ := strconv.Atoi(p)
   333  
   334  	connInfo := &connectionInfo{
   335  		User:     "user",
   336  		Password: "pass",
   337  		Host:     host,
   338  		HostKey:  pubKey,
   339  		Port:     port,
   340  		Timeout:  "30s",
   341  	}
   342  
   343  	cfg, err := prepareSSHConfig(connInfo)
   344  	if err != nil {
   345  		t.Fatal(err)
   346  	}
   347  
   348  	c := &Communicator{
   349  		connInfo: connInfo,
   350  		config:   cfg,
   351  	}
   352  
   353  	var cmd remote.Cmd
   354  	stdout := new(bytes.Buffer)
   355  	cmd.Command = "echo foo"
   356  	cmd.Stdout = stdout
   357  
   358  	if err := c.Start(&cmd); err != nil {
   359  		t.Fatal(err)
   360  	}
   361  	if err := c.Disconnect(); err != nil {
   362  		t.Fatal(err)
   363  	}
   364  
   365  	// now check with the wrong HostKey
   366  	address = newMockLineServer(t, nil, testClientPublicKey)
   367  	_, p, _ = net.SplitHostPort(address)
   368  	port, _ = strconv.Atoi(p)
   369  
   370  	connInfo.HostKey = testClientPublicKey
   371  	connInfo.Port = port
   372  
   373  	cfg, err = prepareSSHConfig(connInfo)
   374  	if err != nil {
   375  		t.Fatal(err)
   376  	}
   377  
   378  	c = &Communicator{
   379  		connInfo: connInfo,
   380  		config:   cfg,
   381  	}
   382  
   383  	err = c.Start(&cmd)
   384  	if err == nil || !strings.Contains(err.Error(), "mismatch") {
   385  		t.Fatalf("expected host key mismatch, got error:%v", err)
   386  	}
   387  }
   388  
   389  func TestHostCert(t *testing.T) {
   390  	pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(testServerHostCert))
   391  	if err != nil {
   392  		t.Fatal(err)
   393  	}
   394  
   395  	signer, err := ssh.ParsePrivateKey([]byte(testServerPrivateKey))
   396  	if err != nil {
   397  		t.Fatal(err)
   398  	}
   399  
   400  	signer, err = ssh.NewCertSigner(pk.(*ssh.Certificate), signer)
   401  	if err != nil {
   402  		t.Fatal(err)
   403  	}
   404  
   405  	address := newMockLineServer(t, signer, testClientPublicKey)
   406  	host, p, _ := net.SplitHostPort(address)
   407  	port, _ := strconv.Atoi(p)
   408  
   409  	connInfo := &connectionInfo{
   410  		User:     "user",
   411  		Password: "pass",
   412  		Host:     host,
   413  		HostKey:  testCAPublicKey,
   414  		Port:     port,
   415  		Timeout:  "30s",
   416  	}
   417  
   418  	cfg, err := prepareSSHConfig(connInfo)
   419  	if err != nil {
   420  		t.Fatal(err)
   421  	}
   422  
   423  	c := &Communicator{
   424  		connInfo: connInfo,
   425  		config:   cfg,
   426  	}
   427  
   428  	var cmd remote.Cmd
   429  	stdout := new(bytes.Buffer)
   430  	cmd.Command = "echo foo"
   431  	cmd.Stdout = stdout
   432  
   433  	if err := c.Start(&cmd); err != nil {
   434  		t.Fatal(err)
   435  	}
   436  	if err := c.Disconnect(); err != nil {
   437  		t.Fatal(err)
   438  	}
   439  
   440  	// now check with the wrong HostKey
   441  	address = newMockLineServer(t, signer, testClientPublicKey)
   442  	_, p, _ = net.SplitHostPort(address)
   443  	port, _ = strconv.Atoi(p)
   444  
   445  	connInfo.HostKey = testClientPublicKey
   446  	connInfo.Port = port
   447  
   448  	cfg, err = prepareSSHConfig(connInfo)
   449  	if err != nil {
   450  		t.Fatal(err)
   451  	}
   452  
   453  	c = &Communicator{
   454  		connInfo: connInfo,
   455  		config:   cfg,
   456  	}
   457  
   458  	err = c.Start(&cmd)
   459  	if err == nil || !strings.Contains(err.Error(), "authorities") {
   460  		t.Fatalf("expected host key mismatch, got error:%v", err)
   461  	}
   462  }
   463  
   464  const SERVER_PEM = `-----BEGIN RSA PRIVATE KEY-----
   465  MIIEpAIBAAKCAQEA8CkDr7uxCFt6lQUVwS8NyPO+fQNxORoGnMnN/XhVJZvpqyKR
   466  Uji9R0d8D66bYxUUsabXjP2y4HTVzbZtnvXFZZshk0cOtJjjekpYJaLK2esPR/iX
   467  wvSltNkrDQDPN/RmgEEMIevW8AgrPsqrnybFHxTpd7rEUHXBOe4nMNRIg3XHykB6
   468  jZk8q5bBPUe3I/f0DK5TJEBpTc6dO3P/j93u55VUqr39/SPRHnld2mCw+c8v6UOh
   469  sssO/DIZFPScD3DYqsk2N+/nz9zXfcOTdWGhawgxuIo1DTokrNQbG3pDrLqcWgqj
   470  13vqJFCmRA0O2CQIwJePd6+Np/XO3Uh/KL6FlQIDAQABAoIBAQCmvQMXNmvCDqk7
   471  30zsVDvw4fHGH+azK3Od1aqTqcEMHISOUbCtckFPxLzIsoSltRQqB1kuRVG07skm
   472  Stsu+xny4lLcSwBVuLRuykEK2EyYIc/5Owo6y9pkhkaSf5ZfFes4bnD6+B/BhRpp
   473  PRMMq0E+xCkX/G6iIi9mhgdlqm0x/vKtjzQeeshw9+gRcRLUpX+UeKFKXMXcDayx
   474  qekr1bAaQKNBhTK+CbZjcqzG4f+BXVGRTZ9nsPAV+yTnWUCU0TghwPmtthHbebqa
   475  9hlkum7qik/bQj/tjJ8/b0vTfHQSVxhtPG/ZV2Tn9ZuL/vrkYqeyMU8XkJ/uaEvH
   476  WPyOcB4BAoGBAP5o5JSEtPog+U3JFrLNSRjz5ofZNVkJzice+0XyqlzJDHhX5tF8
   477  mriYQZLLXYhckBm4IdkhTn/dVbXNQTzyy2WVuO5nU8bkCMvGL9CGpW4YGqwGf7NX
   478  e4H3emtRjLv8VZpUHe/RUUDhmYvMSt1qmXuskfpROuGfLhQBUd6A4J+BAoGBAPGp
   479  UcMKjrxZ5qjYU6DLgS+xeca4Eu70HgdbSQbRo45WubXjyXvTRFij36DrpxJWf1D7
   480  lIsyBifoTra/lAuC1NQXGYWjTCdk2ey8Ll5qOgiXvE6lINHABr+U/Z90/g6LuML2
   481  VzaZbq/QLcT3yVsdyTogKckzCaKsCpusyHE1CXAVAoGAd6kMglKc8N0bhZukgnsN
   482  +5+UeacPcY6sGTh4RWErAjNKGzx1A2lROKvcg9gFaULoQECcIw2IZ5nKW5VsLueg
   483  BWrTrcaJ4A2XmYjhKnp6SvspaGoyHD90hx/Iw7t6r1yzQsB3yDmytwqldtyjBdvC
   484  zynPC2azhDWjraMlR7tka4ECgYAxwvLiHa9sm3qCtCDsUFtmrb3srITBjaUNUL/F
   485  1q8+JR+Sk7gudj9xnTT0VvINNaB71YIt83wPBagHu4VJpYQbtDH+MbUBu6OgOtO1
   486  f1w53rzY2OncJxV8p7pd9mJGLoE6LC2jQY7oRw7Vq0xcJdME1BCmrIrEY3a/vaF8
   487  pjYuTQKBgQCIOH23Xita8KmhH0NdlWxZfcQt1j3AnOcKe6UyN4BsF8hqS7eTA52s
   488  WjG5X2IBl7gs1eMM1qkqR8npS9nwfO/pBmZPwjiZoilypXxWj+c+P3vwre2yija4
   489  bXgFVj4KFBwhr1+8KcobxC0SAPEouMvSkxzjjw+gnebozUtPlud9jA==
   490  -----END RSA PRIVATE KEY-----
   491  `
   492  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`
   493  const CLIENT_PEM = `-----BEGIN RSA PRIVATE KEY-----
   494  MIIEpAIBAAKCAQEAsruexO8yFkchFrmojoopDkmShY28C5KRPX26BXyy7DVCY+M8
   495  PuW2VCbc1ivK6D44C/LNaD53K6OcLk7SNjbuH5KXhStB2996OhMpLLo+DpuPulHp
   496  p9UBHG9G2aivGcoPQ4PXXj+y3hNL+/G+f42WnxYMMb/v7DITND0Q9+DQg2HGJpJB
   497  5b0LUFhQHWz1woIITm3nWcmHaKmVboU7IjzFrUEv3HPTOLb+pjzrkJYWbd0fzof5
   498  MFMruOaBkW+W4TuCYnKMxUt6HgpaVPlDG4LJZMwKSasWQVje/jwxUmYSpNhrJcns
   499  j+LB9xN9GdNMlYv5UGIJkLAfh4OgpXpjUDDbvwIDAQABAoIBAEu2ctFVyk/pnbi0
   500  uRR4rl+hBvKQUeJNGj2ELvL4Ggs5nIAX2IOEZ7JKLC6FqpSrFq7pEd5g57aSvixX
   501  s3DH4CN7w7fj1ShBCNPlHgIWewdRGpeA74vrDWdwNAEsFdDE6aZeCTOhpDGy1vNJ
   502  OrtpzS5i9pN0jTvvEneEjtWSZIHiiVlN+0hsFaiwZ6KXON+sDccZPmnP6Fzwj5Rc
   503  WS0dKSwnxnx0otWgwWFs8nr306nSeMsNmQkHsS9lz4DEVpp9owdzrX1JmbQvNYAV
   504  ohmB3ET4JYFgerqPXJfed9poueGuWCP6MYhsjNeHN35QhofxdO5/0i3JlZfqwZei
   505  tNq/0oECgYEA6SqjRqDiIp3ajwyB7Wf0cIQG/P6JZDyN1jl//htgniliIH5UP1Tm
   506  uAMG5MincV6X9lOyXyh6Yofu5+NR0yt9SqbDZVJ3ZCxKTun7pxJvQFd7wl5bMkiJ
   507  qVfS08k6gQHHDoO+eel+DtpIfWc+e3tvX0aihSU0GZEMqDXYkkphLGECgYEAxDxb
   508  +JwJ3N5UEjjkuvFBpuJnmjIaN9HvQkTv3inlx1gLE4iWBZXXsu4aWF8MCUeAAZyP
   509  42hQDSkCYX/A22tYCEn/jfrU6A+6rkWBTjdUlYLvlSkhosSnO+117WEItb5cUE95
   510  hF4UY7LNs1AsDkV4WE87f/EjpxSwUAjB2Lfd/B8CgYAJ/JiHsuZcozQ0Qk3iVDyF
   511  ATKnbWOHFozgqw/PW27U92LLj32eRM2o/gAylmGNmoaZt1YBe2NaiwXxiqv7hnZU
   512  VzYxRcn1UWxRWvY7Xq/DKrwTRCVVzwOObEOMbKcD1YaoGX50DEso6bKHJH/pnAzW
   513  INlfKIvFuI+5OK0w/tyQoQKBgQCf/jpaOxaLfrV62eobRQJrByLDBGB97GsvU7di
   514  IjTWz8DQH0d5rE7d8uWF8ZCFrEcAiV6DYZQK9smbJqbd/uoacAKtBro5rkFdPwwK
   515  8m/DKqsdqRhkdgOHh7bjYH7Sdy8ax4Fi27WyB6FQtmgFBrz0+zyetsODwQlzZ4Bs
   516  qpSRrwKBgQC0vWHrY5aGIdF+b8EpP0/SSLLALpMySHyWhDyxYcPqdhszYbjDcavv
   517  xrrLXNUD2duBHKPVYE+7uVoDkpZXLUQ4x8argo/IwQM6Kh2ma1y83TYMT6XhL1+B
   518  5UPcl6RXZBCkiU7nFIG6/0XKFqVWc3fU8e09X+iJwXIJ5Jatywtg+g==
   519  -----END RSA PRIVATE KEY-----
   520  `
   521  
   522  func TestCertificateBasedAuth(t *testing.T) {
   523  	signer, err := ssh.ParsePrivateKey([]byte(SERVER_PEM))
   524  	if err != nil {
   525  		t.Fatalf("unable to parse private key: %v", err)
   526  	}
   527  	address := newMockLineServer(t, signer, CLIENT_CERT_SIGNED_BY_SERVER)
   528  	host, p, _ := net.SplitHostPort(address)
   529  	port, _ := strconv.Atoi(p)
   530  
   531  	connInfo := &connectionInfo{
   532  		User:        "user",
   533  		Host:        host,
   534  		PrivateKey:  CLIENT_PEM,
   535  		Certificate: CLIENT_CERT_SIGNED_BY_SERVER,
   536  		Port:        port,
   537  		Timeout:     "30s",
   538  	}
   539  
   540  	cfg, err := prepareSSHConfig(connInfo)
   541  	if err != nil {
   542  		t.Fatal(err)
   543  	}
   544  
   545  	c := &Communicator{
   546  		connInfo: connInfo,
   547  		config:   cfg,
   548  	}
   549  
   550  	var cmd remote.Cmd
   551  	stdout := new(bytes.Buffer)
   552  	cmd.Command = "echo foo"
   553  	cmd.Stdout = stdout
   554  
   555  	if err := c.Start(&cmd); err != nil {
   556  		t.Fatal(err)
   557  	}
   558  	if err := c.Disconnect(); err != nil {
   559  		t.Fatal(err)
   560  	}
   561  }
   562  
   563  func TestAccUploadFile(t *testing.T) {
   564  	// use the local ssh server and scp binary to check uploads
   565  	if ok := os.Getenv("SSH_UPLOAD_TEST"); ok == "" {
   566  		t.Log("Skipping Upload Acceptance without SSH_UPLOAD_TEST set")
   567  		t.Skip()
   568  	}
   569  
   570  	r := &terraform.InstanceState{
   571  		Ephemeral: terraform.EphemeralState{
   572  			ConnInfo: map[string]string{
   573  				"type":    "ssh",
   574  				"user":    os.Getenv("USER"),
   575  				"host":    "127.0.0.1",
   576  				"port":    "22",
   577  				"timeout": "30s",
   578  			},
   579  		},
   580  	}
   581  
   582  	c, err := New(r)
   583  	if err != nil {
   584  		t.Fatalf("error creating communicator: %s", err)
   585  	}
   586  
   587  	tmpDir, err := ioutil.TempDir("", "communicator")
   588  	if err != nil {
   589  		t.Fatal(err)
   590  	}
   591  	defer os.RemoveAll(tmpDir)
   592  
   593  	content := []byte("this is the file content")
   594  	source := bytes.NewReader(content)
   595  	tmpFile := filepath.Join(tmpDir, "tempFile.out")
   596  	err = c.Upload(tmpFile, source)
   597  	if err != nil {
   598  		t.Fatalf("error uploading file: %s", err)
   599  	}
   600  
   601  	data, err := ioutil.ReadFile(tmpFile)
   602  	if err != nil {
   603  		t.Fatal(err)
   604  	}
   605  
   606  	if !bytes.Equal(data, content) {
   607  		t.Fatalf("bad: %s", data)
   608  	}
   609  }
   610  
   611  func TestAccHugeUploadFile(t *testing.T) {
   612  	// use the local ssh server and scp binary to check uploads
   613  	if ok := os.Getenv("SSH_UPLOAD_TEST"); ok == "" {
   614  		t.Log("Skipping Upload Acceptance without SSH_UPLOAD_TEST set")
   615  		t.Skip()
   616  	}
   617  
   618  	r := &terraform.InstanceState{
   619  		Ephemeral: terraform.EphemeralState{
   620  			ConnInfo: map[string]string{
   621  				"type":    "ssh",
   622  				"user":    os.Getenv("USER"),
   623  				"host":    "127.0.0.1",
   624  				"port":    "22",
   625  				"timeout": "30s",
   626  			},
   627  		},
   628  	}
   629  
   630  	c, err := New(r)
   631  	if err != nil {
   632  		t.Fatalf("error creating communicator: %s", err)
   633  	}
   634  
   635  	// copy 4GB of data, random to prevent compression.
   636  	size := int64(1 << 32)
   637  	source := io.LimitReader(rand.New(rand.NewSource(0)), size)
   638  
   639  	dest, err := ioutil.TempFile("", "communicator")
   640  	if err != nil {
   641  		t.Fatal(err)
   642  	}
   643  	destName := dest.Name()
   644  	dest.Close()
   645  	defer os.Remove(destName)
   646  
   647  	t.Log("Uploading to", destName)
   648  
   649  	// bypass the Upload method so we can directly supply the file size
   650  	// preventing the extra copy of the huge file.
   651  	targetDir := filepath.Dir(destName)
   652  	targetFile := filepath.Base(destName)
   653  
   654  	scpFunc := func(w io.Writer, stdoutR *bufio.Reader) error {
   655  		return scpUploadFile(targetFile, source, w, stdoutR, size)
   656  	}
   657  
   658  	err = c.scpSession("scp -vt "+targetDir, scpFunc)
   659  	if err != nil {
   660  		t.Fatal(err)
   661  	}
   662  
   663  	// check the final file size
   664  	fs, err := os.Stat(destName)
   665  	if err != nil {
   666  		t.Fatal(err)
   667  	}
   668  
   669  	if fs.Size() != size {
   670  		t.Fatalf("expected file size of %d, got %d", size, fs.Size())
   671  	}
   672  }
   673  
   674  func TestScriptPath(t *testing.T) {
   675  	cases := []struct {
   676  		Input   string
   677  		Pattern string
   678  	}{
   679  		{
   680  			"/tmp/script.sh",
   681  			`^/tmp/script\.sh$`,
   682  		},
   683  		{
   684  			"/tmp/script_%RAND%.sh",
   685  			`^/tmp/script_(\d+)\.sh$`,
   686  		},
   687  	}
   688  
   689  	for _, tc := range cases {
   690  		r := &terraform.InstanceState{
   691  			Ephemeral: terraform.EphemeralState{
   692  				ConnInfo: map[string]string{
   693  					"type":        "ssh",
   694  					"script_path": tc.Input,
   695  				},
   696  			},
   697  		}
   698  		comm, err := New(r)
   699  		if err != nil {
   700  			t.Fatalf("err: %s", err)
   701  		}
   702  		output := comm.ScriptPath()
   703  
   704  		match, err := regexp.Match(tc.Pattern, []byte(output))
   705  		if err != nil {
   706  			t.Fatalf("bad: %s\n\nerr: %s", tc.Input, err)
   707  		}
   708  		if !match {
   709  			t.Fatalf("bad: %s\n\n%s", tc.Input, output)
   710  		}
   711  	}
   712  }
   713  
   714  func TestScriptPath_randSeed(t *testing.T) {
   715  	// Pre GH-4186 fix, this value was the deterministic start the pseudorandom
   716  	// chain of unseeded math/rand values for Int31().
   717  	staticSeedPath := "/tmp/terraform_1298498081.sh"
   718  	c, err := New(&terraform.InstanceState{})
   719  	if err != nil {
   720  		t.Fatalf("err: %s", err)
   721  	}
   722  	path := c.ScriptPath()
   723  	if path == staticSeedPath {
   724  		t.Fatalf("rand not seeded! got: %s", path)
   725  	}
   726  }
   727  
   728  const testClientPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
   729  MIIEpQIBAAKCAQEAxOgNXOJ/jrRDxBZTSk2X9otNy9zcpUmJr5ifDi5sy7j2ZiQS
   730  beBt1Wf+tLNWis8Cyq06ttEvjjRuM75yucyD6GrqDTXVCSm4PeOIQeDhPhw26wYZ
   731  O0h/mFgrAiCwaEl8AFXVBInOhVn/0nqgUpkwckh76bjTsNeifkiugK3cfJOuBdrU
   732  ZGbgugJjmYo4Cmv7ECo1gCFT5N+BAjOji3z3N5ClBH5HaWC77jH7kTH0k5cZ+ZRQ
   733  tG9EqLyvWlnzTAR/Yly0JInkOa16Ms1Au5sIJwEoJfHKsNVK06IlLob53nblwYW0
   734  H5gv1Kb/rS+nUkpPtA5YFShB7iZnPLPPv6qXSwIDAQABAoIBAC0UY1rMkB9/rbQK
   735  2G6+bPgI1HrDydAdkeQdsOxyPH43jlG8GGwHYZ3l/S4pkLqewijcmACay6Rm5IP8
   736  Kg/XfquLLqJvnKJIZuHkYaGTdn3dv8T21Hf6FRwvs0j9auW1TSpWfDpZwmpNPIBX
   737  irTeVXUUmynbIrvt4km/IhRbuYrbbb964CLYD1DCl3XssXxoRNvPpc5EtOuyDorA
   738  5g1hvZR1FqbOAmOuNQMYJociMuWB8mCaHb+o1Sg4A65OLXxoKs0cuwInJ/n/R4Z3
   739  +GrV+x5ypBMxXgjjQtKMLEOujkvxs1cp34hkbhKMHHXxbMu5jl74YtGGsLLk90rq
   740  ieZGIgECgYEA49OM9mMCrDoFUTZdJaSARA/MOXkdQgrqVTv9kUHee7oeMZZ6lS0i
   741  bPU7g+Bq+UAN0qcw9x992eAElKjBA71Q5UbZYWh29BDMZd8bRJmwz4P6aSMoYLWI
   742  Sr31caJU9LdmPFatarNeehjSJtlTuoZD9+NElnnUwNaTeOOo5UdhTQsCgYEA3UGm
   743  QWoDUttFwK9oL2KL8M54Bx6EzNhnyk03WrqBbR7PJcPKnsF0R/0soQ+y0FW0r8RJ
   744  TqG6ze5fUJII72B4GlMTQdP+BIvaKQttwWQTNIjbbv4NksF445gdVOO1xi9SvQ7k
   745  uvMVxOb+1jL3HAFa3furWu2tJRDs6dhuaILLxsECgYEAhnhlKUBDYZhVbxvhWsh/
   746  lKymY/3ikQqUSX7BKa1xPiIalDY3YDllql4MpMgfG8L85asdMZ96ztB0o7H/Ss/B
   747  IbLxt5bLLz+DBVXsaE82lyVU9h10RbCgI01/w3SHJHHjfBXFAcehKfvgfmGkE+IP
   748  2A5ie1aphrCgFqh5FetNuQUCgYEAibL42I804FUtFR1VduAa/dRRqQSaW6528dWa
   749  lLGsKRBalUNEEAeP6dmr89UEUVp1qEo94V0QGGe5FDi+rNPaC3AWdQqNdaDgNlkx
   750  hoFU3oYqIuqj4ejc5rBd2N4a2+vJz3W8bokozDGC+iYf2mMRfUPKwj1XW9Er0OFs
   751  3UhBsEECgYEAto/iJB7ZlCM7EyV9JW0tsEt83rbKMQ/Ex0ShbBIejej0Xx7bwx60
   752  tVgay+bzJnNkXu6J4XVI98A/WsdI2kW4hL0STYdHV5HVA1l87V4ZbvTF2Bx8a8RJ
   753  OF3UjpMTWKqOprw9nAu5VuwNRVzORF8ER8rgGeaR2/gsSvIYFy9VXq8=
   754  -----END RSA PRIVATE KEY-----`
   755  
   756  var testClientPublicKey = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDE6A1c4n+OtEPEFlNKTZf2i03L3NylSYmvmJ8OLmzLuPZmJBJt4G3VZ/60s1aKzwLKrTq20S+ONG4zvnK5zIPoauoNNdUJKbg944hB4OE+HDbrBhk7SH+YWCsCILBoSXwAVdUEic6FWf/SeqBSmTBySHvpuNOw16J+SK6Ardx8k64F2tRkZuC6AmOZijgKa/sQKjWAIVPk34ECM6OLfPc3kKUEfkdpYLvuMfuRMfSTlxn5lFC0b0SovK9aWfNMBH9iXLQkieQ5rXoyzUC7mwgnASgl8cqw1UrToiUuhvneduXBhbQfmC/Upv+tL6dSSk+0DlgVKEHuJmc8s8+/qpdL`
   757  
   758  func acceptUserPass(goodUser, goodPass string) func(ssh.ConnMetadata, []byte) (*ssh.Permissions, error) {
   759  	return func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
   760  		if c.User() == goodUser && string(pass) == goodPass {
   761  			return nil, nil
   762  		}
   763  		return nil, fmt.Errorf("password rejected for %q", c.User())
   764  	}
   765  }
   766  
   767  func acceptPublicKey(keystr string) func(ssh.ConnMetadata, ssh.PublicKey) (*ssh.Permissions, error) {
   768  	return func(_ ssh.ConnMetadata, inkey ssh.PublicKey) (*ssh.Permissions, error) {
   769  		goodkey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(keystr))
   770  		if err != nil {
   771  			return nil, fmt.Errorf("error parsing key: %v", err)
   772  		}
   773  
   774  		if bytes.Equal(inkey.Marshal(), goodkey.Marshal()) {
   775  			return nil, nil
   776  		}
   777  
   778  		return nil, fmt.Errorf("public key rejected")
   779  	}
   780  }