github.com/opentofu/opentofu@v1.7.1/internal/communicator/ssh/communicator_test.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  //go:build !race
     7  // +build !race
     8  
     9  package ssh
    10  
    11  import (
    12  	"bufio"
    13  	"bytes"
    14  	"encoding/base64"
    15  	"fmt"
    16  	"io"
    17  	"math/rand"
    18  	"net"
    19  	"os"
    20  	"path/filepath"
    21  	"regexp"
    22  	"strconv"
    23  	"strings"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/opentofu/opentofu/internal/communicator/remote"
    28  	"github.com/zclconf/go-cty/cty"
    29  	"golang.org/x/crypto/ssh"
    30  )
    31  
    32  // private key for mock server
    33  const testServerPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
    34  MIIEpAIBAAKCAQEA19lGVsTqIT5iiNYRgnoY1CwkbETW5cq+Rzk5v/kTlf31XpSU
    35  70HVWkbTERECjaYdXM2gGcbb+sxpq6GtXf1M3kVomycqhxwhPv4Cr6Xp4WT/jkFx
    36  9z+FFzpeodGJWjOH6L2H5uX1Cvr9EDdQp9t9/J32/qBFntY8GwoUI/y/1MSTmMiF
    37  tupdMODN064vd3gyMKTwrlQ8tZM6aYuyOPsutLlUY7M5x5FwMDYvnPDSeyT/Iw0z
    38  s3B+NCyqeeMd2T7YzQFnRATj0M7rM5LoSs7DVqVriOEABssFyLj31PboaoLhOKgc
    39  qoM9khkNzr7FHVvi+DhYM2jD0DwvqZLN6NmnLwIDAQABAoIBAQCGVj+kuSFOV1lT
    40  +IclQYA6bM6uY5mroqcSBNegVxCNhWU03BxlW//BE9tA/+kq53vWylMeN9mpGZea
    41  riEMIh25KFGWXqXlOOioH8bkMsqA8S7sBmc7jljyv+0toQ9vCCtJ+sueNPhxQQxH
    42  D2YvUjfzBQ04I9+wn30BByDJ1QA/FoPsunxIOUCcRBE/7jxuLYcpR+JvEF68yYIh
    43  atXRld4W4in7T65YDR8jK1Uj9XAcNeDYNpT/M6oFLx1aPIlkG86aCWRO19S1jLPT
    44  b1ZAKHHxPMCVkSYW0RqvIgLXQOR62D0Zne6/2wtzJkk5UCjkSQ2z7ZzJpMkWgDgN
    45  ifCULFPBAoGBAPoMZ5q1w+zB+knXUD33n1J+niN6TZHJulpf2w5zsW+m2K6Zn62M
    46  MXndXlVAHtk6p02q9kxHdgov34Uo8VpuNjbS1+abGFTI8NZgFo+bsDxJdItemwC4
    47  KJ7L1iz39hRN/ZylMRLz5uTYRGddCkeIHhiG2h7zohH/MaYzUacXEEy3AoGBANz8
    48  e/msleB+iXC0cXKwds26N4hyMdAFE5qAqJXvV3S2W8JZnmU+sS7vPAWMYPlERPk1
    49  D8Q2eXqdPIkAWBhrx4RxD7rNc5qFNcQWEhCIxC9fccluH1y5g2M+4jpMX2CT8Uv+
    50  3z+NoJ5uDTXZTnLCfoZzgZ4nCZVZ+6iU5U1+YXFJAoGBANLPpIV920n/nJmmquMj
    51  orI1R/QXR9Cy56cMC65agezlGOfTYxk5Cfl5Ve+/2IJCfgzwJyjWUsFx7RviEeGw
    52  64o7JoUom1HX+5xxdHPsyZ96OoTJ5RqtKKoApnhRMamau0fWydH1yeOEJd+TRHhc
    53  XStGfhz8QNa1dVFvENczja1vAoGABGWhsd4VPVpHMc7lUvrf4kgKQtTC2PjA4xoc
    54  QJ96hf/642sVE76jl+N6tkGMzGjnVm4P2j+bOy1VvwQavKGoXqJBRd5Apppv727g
    55  /SM7hBXKFc/zH80xKBBgP/i1DR7kdjakCoeu4ngeGywvu2jTS6mQsqzkK+yWbUxJ
    56  I7mYBsECgYB/KNXlTEpXtz/kwWCHFSYA8U74l7zZbVD8ul0e56JDK+lLcJ0tJffk
    57  gqnBycHj6AhEycjda75cs+0zybZvN4x65KZHOGW/O/7OAWEcZP5TPb3zf9ned3Hl
    58  NsZoFj52ponUM6+99A2CmezFCN16c4mbA//luWF+k3VVqR6BpkrhKw==
    59  -----END RSA PRIVATE KEY-----`
    60  
    61  // this cert was signed by the key from testCAPublicKey
    62  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=`
    63  
    64  const testCAPublicKey = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCrozyZIhdEvalCn+eSzHH94cO9ykiywA13ntWI7mJcHBwYTeCYWG8E9zGXyp2iDOjCGudM0Tdt8o0OofKChk9Z/qiUN0G8y1kmaXBlBM3qA5R9NPpvMYMNkYLfX6ivtZCnqrsbzaoqN2Oc/7H2StHzJWh/XCGu9otQZA6vdv1oSmAsZOjw/xIGaGQqDUaLq21J280PP1qSbdJHf76iSHE+TWe3YpqV946JWM5tCh0DykZ10VznvxYpUjzhr07IN3tVKxOXbPnnU7lX6IaLIWgfzLqwSyheeux05c3JLF9iF4sFu8ou4hwQz1iuUTU1jxgwZP0w/bkXgFFs0949lW81`
    65  
    66  func newMockLineServer(t *testing.T, signer ssh.Signer, pubKey string) string {
    67  	serverConfig := &ssh.ServerConfig{
    68  		PasswordCallback:  acceptUserPass("user", "pass"),
    69  		PublicKeyCallback: acceptPublicKey(pubKey),
    70  	}
    71  
    72  	var err error
    73  	if signer == nil {
    74  		signer, err = ssh.ParsePrivateKey([]byte(testServerPrivateKey))
    75  		if err != nil {
    76  			t.Fatalf("unable to parse private key: %s", err)
    77  		}
    78  	}
    79  	serverConfig.AddHostKey(signer)
    80  
    81  	l, err := net.Listen("tcp", "127.0.0.1:0")
    82  	if err != nil {
    83  		t.Fatalf("Unable to listen for connection: %s", err)
    84  	}
    85  
    86  	go func() {
    87  		defer l.Close()
    88  		c, err := l.Accept()
    89  		if err != nil {
    90  			t.Errorf("Unable to accept incoming connection: %s", err)
    91  		}
    92  		defer c.Close()
    93  		conn, chans, _, err := ssh.NewServerConn(c, serverConfig)
    94  		if err != nil {
    95  			t.Logf("Handshaking error: %v", err)
    96  		}
    97  		t.Log("Accepted SSH connection")
    98  
    99  		for newChannel := range chans {
   100  			channel, requests, err := newChannel.Accept()
   101  			if err != nil {
   102  				t.Errorf("Unable to accept channel.")
   103  			}
   104  			t.Log("Accepted channel")
   105  
   106  			go func(in <-chan *ssh.Request) {
   107  				defer channel.Close()
   108  				for req := range in {
   109  					// since this channel's requests are serviced serially,
   110  					// this will block keepalive probes, and can simulate a
   111  					// hung connection.
   112  					if bytes.Contains(req.Payload, []byte("sleep")) {
   113  						time.Sleep(time.Second)
   114  					}
   115  
   116  					if req.WantReply {
   117  						req.Reply(true, nil)
   118  					}
   119  				}
   120  			}(requests)
   121  		}
   122  		conn.Close()
   123  	}()
   124  
   125  	return l.Addr().String()
   126  }
   127  
   128  func TestNew_Invalid(t *testing.T) {
   129  	address := newMockLineServer(t, nil, testClientPublicKey)
   130  	parts := strings.Split(address, ":")
   131  
   132  	v := cty.ObjectVal(map[string]cty.Value{
   133  		"type":     cty.StringVal("ssh"),
   134  		"user":     cty.StringVal("user"),
   135  		"password": cty.StringVal("i-am-invalid"),
   136  		"host":     cty.StringVal(parts[0]),
   137  		"port":     cty.StringVal(parts[1]),
   138  		"timeout":  cty.StringVal("30s"),
   139  	})
   140  
   141  	c, err := New(v)
   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 TestNew_InvalidHost(t *testing.T) {
   153  	v := cty.ObjectVal(map[string]cty.Value{
   154  		"type":     cty.StringVal("ssh"),
   155  		"user":     cty.StringVal("user"),
   156  		"password": cty.StringVal("i-am-invalid"),
   157  		"port":     cty.StringVal("22"),
   158  		"timeout":  cty.StringVal("30s"),
   159  	})
   160  
   161  	_, err := New(v)
   162  	if err == nil {
   163  		t.Fatal("should have had an error creating communicator")
   164  	}
   165  }
   166  
   167  func TestStart(t *testing.T) {
   168  	address := newMockLineServer(t, nil, testClientPublicKey)
   169  	parts := strings.Split(address, ":")
   170  
   171  	v := cty.ObjectVal(map[string]cty.Value{
   172  		"type":     cty.StringVal("ssh"),
   173  		"user":     cty.StringVal("user"),
   174  		"password": cty.StringVal("pass"),
   175  		"host":     cty.StringVal(parts[0]),
   176  		"port":     cty.StringVal(parts[1]),
   177  		"timeout":  cty.StringVal("30s"),
   178  	})
   179  
   180  	c, err := New(v)
   181  	if err != nil {
   182  		t.Fatalf("error creating communicator: %s", err)
   183  	}
   184  
   185  	var cmd remote.Cmd
   186  	stdout := new(bytes.Buffer)
   187  	cmd.Command = "echo foo"
   188  	cmd.Stdout = stdout
   189  
   190  	err = c.Start(&cmd)
   191  	if err != nil {
   192  		t.Fatalf("error executing remote command: %s", err)
   193  	}
   194  }
   195  
   196  // TestKeepAlives verifies that the keepalive messages don't interfere with
   197  // normal operation of the client.
   198  func TestKeepAlives(t *testing.T) {
   199  	ivl := keepAliveInterval
   200  	keepAliveInterval = 250 * time.Millisecond
   201  	defer func() { keepAliveInterval = ivl }()
   202  
   203  	address := newMockLineServer(t, nil, testClientPublicKey)
   204  	parts := strings.Split(address, ":")
   205  
   206  	v := cty.ObjectVal(map[string]cty.Value{
   207  		"type":     cty.StringVal("ssh"),
   208  		"user":     cty.StringVal("user"),
   209  		"password": cty.StringVal("pass"),
   210  		"host":     cty.StringVal(parts[0]),
   211  		"port":     cty.StringVal(parts[1]),
   212  	})
   213  
   214  	c, err := New(v)
   215  	if err != nil {
   216  		t.Fatalf("error creating communicator: %s", err)
   217  	}
   218  
   219  	if err := c.Connect(nil); err != nil {
   220  		t.Fatal(err)
   221  	}
   222  
   223  	var cmd remote.Cmd
   224  	stdout := new(bytes.Buffer)
   225  	cmd.Command = "sleep"
   226  	cmd.Stdout = stdout
   227  
   228  	// wait a bit before executing the command, so that at least 1 keepalive is sent
   229  	time.Sleep(500 * time.Millisecond)
   230  
   231  	err = c.Start(&cmd)
   232  	if err != nil {
   233  		t.Fatalf("error executing remote command: %s", err)
   234  	}
   235  }
   236  
   237  // TestDeadConnection verifies that failed keepalive messages will eventually
   238  // kill the connection.
   239  func TestFailedKeepAlives(t *testing.T) {
   240  	ivl := keepAliveInterval
   241  	del := maxKeepAliveDelay
   242  	maxKeepAliveDelay = 500 * time.Millisecond
   243  	keepAliveInterval = 250 * time.Millisecond
   244  	defer func() {
   245  		keepAliveInterval = ivl
   246  		maxKeepAliveDelay = del
   247  	}()
   248  
   249  	address := newMockLineServer(t, nil, testClientPublicKey)
   250  	parts := strings.Split(address, ":")
   251  
   252  	v := cty.ObjectVal(map[string]cty.Value{
   253  		"type":     cty.StringVal("ssh"),
   254  		"user":     cty.StringVal("user"),
   255  		"password": cty.StringVal("pass"),
   256  		"host":     cty.StringVal(parts[0]),
   257  		"port":     cty.StringVal(parts[1]),
   258  		"timeout":  cty.StringVal("30s"),
   259  	})
   260  
   261  	c, err := New(v)
   262  	if err != nil {
   263  		t.Fatalf("error creating communicator: %s", err)
   264  	}
   265  
   266  	if err := c.Connect(nil); err != nil {
   267  		t.Fatal(err)
   268  	}
   269  	var cmd remote.Cmd
   270  	stdout := new(bytes.Buffer)
   271  	cmd.Command = "sleep"
   272  	cmd.Stdout = stdout
   273  
   274  	err = c.Start(&cmd)
   275  	if err == nil {
   276  		t.Fatal("expected connection error")
   277  	}
   278  }
   279  
   280  func TestLostConnection(t *testing.T) {
   281  	address := newMockLineServer(t, nil, testClientPublicKey)
   282  	parts := strings.Split(address, ":")
   283  
   284  	v := cty.ObjectVal(map[string]cty.Value{
   285  		"type":     cty.StringVal("ssh"),
   286  		"user":     cty.StringVal("user"),
   287  		"password": cty.StringVal("pass"),
   288  		"host":     cty.StringVal(parts[0]),
   289  		"port":     cty.StringVal(parts[1]),
   290  		"timeout":  cty.StringVal("30s"),
   291  	})
   292  
   293  	c, err := New(v)
   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:     uint16(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 = uint16(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:     uint16(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 = uint16(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:        uint16(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  	v := cty.ObjectVal(map[string]cty.Value{
   571  		"type":    cty.StringVal("ssh"),
   572  		"user":    cty.StringVal(os.Getenv("USER")),
   573  		"host":    cty.StringVal("127.0.0.1"),
   574  		"port":    cty.StringVal("22"),
   575  		"timeout": cty.StringVal("30s"),
   576  	})
   577  
   578  	c, err := New(v)
   579  	if err != nil {
   580  		t.Fatalf("error creating communicator: %s", err)
   581  	}
   582  
   583  	tmpDir := t.TempDir()
   584  	source, err := os.CreateTemp(tmpDir, "tempfile.in")
   585  	if err != nil {
   586  		t.Fatal(err)
   587  	}
   588  
   589  	content := "this is the file content"
   590  	if _, err := source.WriteString(content); err != nil {
   591  		t.Fatal(err)
   592  	}
   593  	source.Seek(0, io.SeekStart)
   594  
   595  	tmpFile := filepath.Join(tmpDir, "tempFile.out")
   596  
   597  	testUploadSizeHook = func(size int64) {
   598  		if size != int64(len(content)) {
   599  			t.Errorf("expected %d bytes, got %d\n", len(content), size)
   600  		}
   601  	}
   602  	defer func() {
   603  		testUploadSizeHook = nil
   604  	}()
   605  
   606  	err = c.Upload(tmpFile, source)
   607  	if err != nil {
   608  		t.Fatalf("error uploading file: %s", err)
   609  	}
   610  
   611  	data, err := os.ReadFile(tmpFile)
   612  	if err != nil {
   613  		t.Fatal(err)
   614  	}
   615  
   616  	if string(data) != content {
   617  		t.Fatalf("bad: %s", data)
   618  	}
   619  }
   620  
   621  func TestAccHugeUploadFile(t *testing.T) {
   622  	// use the local ssh server and scp binary to check uploads
   623  	if ok := os.Getenv("SSH_UPLOAD_TEST"); ok == "" {
   624  		t.Log("Skipping Upload Acceptance without SSH_UPLOAD_TEST set")
   625  		t.Skip()
   626  	}
   627  
   628  	v := cty.ObjectVal(map[string]cty.Value{
   629  		"type":    cty.StringVal("ssh"),
   630  		"host":    cty.StringVal("127.0.0.1"),
   631  		"user":    cty.StringVal(os.Getenv("USER")),
   632  		"port":    cty.StringVal("22"),
   633  		"timeout": cty.StringVal("30s"),
   634  	})
   635  
   636  	c, err := New(v)
   637  	if err != nil {
   638  		t.Fatalf("error creating communicator: %s", err)
   639  	}
   640  
   641  	// copy 4GB of data, random to prevent compression.
   642  	size := int64(1 << 32)
   643  	source := io.LimitReader(rand.New(rand.NewSource(0)), size)
   644  
   645  	dest, err := os.CreateTemp("", "communicator")
   646  	if err != nil {
   647  		t.Fatal(err)
   648  	}
   649  	destName := dest.Name()
   650  	dest.Close()
   651  	defer os.Remove(destName)
   652  
   653  	t.Log("Uploading to", destName)
   654  
   655  	// bypass the Upload method so we can directly supply the file size
   656  	// preventing the extra copy of the huge file.
   657  	targetDir := filepath.Dir(destName)
   658  	targetFile := filepath.Base(destName)
   659  
   660  	scpFunc := func(w io.Writer, stdoutR *bufio.Reader) error {
   661  		return scpUploadFile(targetFile, source, w, stdoutR, size)
   662  	}
   663  
   664  	cmd, err := quoteShell([]string{"scp", "-vt", targetDir}, c.connInfo.TargetPlatform)
   665  	if err != nil {
   666  		t.Fatal(err)
   667  	}
   668  	err = c.scpSession(cmd, scpFunc)
   669  	if err != nil {
   670  		t.Fatal(err)
   671  	}
   672  
   673  	// check the final file size
   674  	fs, err := os.Stat(destName)
   675  	if err != nil {
   676  		t.Fatal(err)
   677  	}
   678  
   679  	if fs.Size() != size {
   680  		t.Fatalf("expected file size of %d, got %d", size, fs.Size())
   681  	}
   682  }
   683  
   684  func TestScriptPath(t *testing.T) {
   685  	cases := []struct {
   686  		Input   string
   687  		Pattern string
   688  	}{
   689  		{
   690  			"/tmp/script.sh",
   691  			`^/tmp/script\.sh$`,
   692  		},
   693  		{
   694  			"/tmp/script_%RAND%.sh",
   695  			`^/tmp/script_(\d+)\.sh$`,
   696  		},
   697  	}
   698  
   699  	for _, tc := range cases {
   700  		v := cty.ObjectVal(map[string]cty.Value{
   701  			"type":        cty.StringVal("ssh"),
   702  			"host":        cty.StringVal("127.0.0.1"),
   703  			"script_path": cty.StringVal(tc.Input),
   704  		})
   705  
   706  		comm, err := New(v)
   707  		if err != nil {
   708  			t.Fatalf("err: %s", err)
   709  		}
   710  		output := comm.ScriptPath()
   711  
   712  		match, err := regexp.Match(tc.Pattern, []byte(output))
   713  		if err != nil {
   714  			t.Fatalf("bad: %s\n\nerr: %s", tc.Input, err)
   715  		}
   716  		if !match {
   717  			t.Fatalf("bad: %s\n\n%s", tc.Input, output)
   718  		}
   719  	}
   720  }
   721  
   722  func TestScriptPath_randSeed(t *testing.T) {
   723  	// Pre GH-4186 fix, this value was the deterministic start the pseudorandom
   724  	// chain of unseeded math/rand values for Int31().
   725  	staticSeedPath := "/tmp/terraform_1298498081.sh"
   726  	c, err := New(cty.ObjectVal(map[string]cty.Value{
   727  		"type": cty.StringVal("ssh"),
   728  		"host": cty.StringVal("127.0.0.1"),
   729  	}))
   730  	if err != nil {
   731  		t.Fatalf("err: %s", err)
   732  	}
   733  	path := c.ScriptPath()
   734  	if path == staticSeedPath {
   735  		t.Fatalf("rand not seeded! got: %s", path)
   736  	}
   737  }
   738  
   739  var testClientPublicKey = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDE6A1c4n+OtEPEFlNKTZf2i03L3NylSYmvmJ8OLmzLuPZmJBJt4G3VZ/60s1aKzwLKrTq20S+ONG4zvnK5zIPoauoNNdUJKbg944hB4OE+HDbrBhk7SH+YWCsCILBoSXwAVdUEic6FWf/SeqBSmTBySHvpuNOw16J+SK6Ardx8k64F2tRkZuC6AmOZijgKa/sQKjWAIVPk34ECM6OLfPc3kKUEfkdpYLvuMfuRMfSTlxn5lFC0b0SovK9aWfNMBH9iXLQkieQ5rXoyzUC7mwgnASgl8cqw1UrToiUuhvneduXBhbQfmC/Upv+tL6dSSk+0DlgVKEHuJmc8s8+/qpdL`
   740  
   741  func acceptUserPass(goodUser, goodPass string) func(ssh.ConnMetadata, []byte) (*ssh.Permissions, error) {
   742  	return func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
   743  		if c.User() == goodUser && string(pass) == goodPass {
   744  			return nil, nil
   745  		}
   746  		return nil, fmt.Errorf("password rejected for %q", c.User())
   747  	}
   748  }
   749  
   750  func acceptPublicKey(keystr string) func(ssh.ConnMetadata, ssh.PublicKey) (*ssh.Permissions, error) {
   751  	return func(_ ssh.ConnMetadata, inkey ssh.PublicKey) (*ssh.Permissions, error) {
   752  		goodkey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(keystr))
   753  		if err != nil {
   754  			return nil, fmt.Errorf("error parsing key: %w", err)
   755  		}
   756  
   757  		if bytes.Equal(inkey.Marshal(), goodkey.Marshal()) {
   758  			return nil, nil
   759  		}
   760  
   761  		return nil, fmt.Errorf("public key rejected")
   762  	}
   763  }