github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/communicator/ssh/communicator_test.go (about)

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