github.com/nathanielks/terraform@v0.6.1-0.20170509030759-13e1a62319dc/communicator/ssh/communicator_test.go (about)

     1  // +build !race
     2  
     3  package ssh
     4  
     5  import (
     6  	"bufio"
     7  	"bytes"
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"math/rand"
    12  	"net"
    13  	"os"
    14  	"path/filepath"
    15  	"regexp"
    16  	"strings"
    17  	"testing"
    18  
    19  	"github.com/hashicorp/terraform/communicator/remote"
    20  	"github.com/hashicorp/terraform/terraform"
    21  	"golang.org/x/crypto/ssh"
    22  )
    23  
    24  // private key for mock server
    25  const testServerPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
    26  MIIEpAIBAAKCAQEA19lGVsTqIT5iiNYRgnoY1CwkbETW5cq+Rzk5v/kTlf31XpSU
    27  70HVWkbTERECjaYdXM2gGcbb+sxpq6GtXf1M3kVomycqhxwhPv4Cr6Xp4WT/jkFx
    28  9z+FFzpeodGJWjOH6L2H5uX1Cvr9EDdQp9t9/J32/qBFntY8GwoUI/y/1MSTmMiF
    29  tupdMODN064vd3gyMKTwrlQ8tZM6aYuyOPsutLlUY7M5x5FwMDYvnPDSeyT/Iw0z
    30  s3B+NCyqeeMd2T7YzQFnRATj0M7rM5LoSs7DVqVriOEABssFyLj31PboaoLhOKgc
    31  qoM9khkNzr7FHVvi+DhYM2jD0DwvqZLN6NmnLwIDAQABAoIBAQCGVj+kuSFOV1lT
    32  +IclQYA6bM6uY5mroqcSBNegVxCNhWU03BxlW//BE9tA/+kq53vWylMeN9mpGZea
    33  riEMIh25KFGWXqXlOOioH8bkMsqA8S7sBmc7jljyv+0toQ9vCCtJ+sueNPhxQQxH
    34  D2YvUjfzBQ04I9+wn30BByDJ1QA/FoPsunxIOUCcRBE/7jxuLYcpR+JvEF68yYIh
    35  atXRld4W4in7T65YDR8jK1Uj9XAcNeDYNpT/M6oFLx1aPIlkG86aCWRO19S1jLPT
    36  b1ZAKHHxPMCVkSYW0RqvIgLXQOR62D0Zne6/2wtzJkk5UCjkSQ2z7ZzJpMkWgDgN
    37  ifCULFPBAoGBAPoMZ5q1w+zB+knXUD33n1J+niN6TZHJulpf2w5zsW+m2K6Zn62M
    38  MXndXlVAHtk6p02q9kxHdgov34Uo8VpuNjbS1+abGFTI8NZgFo+bsDxJdItemwC4
    39  KJ7L1iz39hRN/ZylMRLz5uTYRGddCkeIHhiG2h7zohH/MaYzUacXEEy3AoGBANz8
    40  e/msleB+iXC0cXKwds26N4hyMdAFE5qAqJXvV3S2W8JZnmU+sS7vPAWMYPlERPk1
    41  D8Q2eXqdPIkAWBhrx4RxD7rNc5qFNcQWEhCIxC9fccluH1y5g2M+4jpMX2CT8Uv+
    42  3z+NoJ5uDTXZTnLCfoZzgZ4nCZVZ+6iU5U1+YXFJAoGBANLPpIV920n/nJmmquMj
    43  orI1R/QXR9Cy56cMC65agezlGOfTYxk5Cfl5Ve+/2IJCfgzwJyjWUsFx7RviEeGw
    44  64o7JoUom1HX+5xxdHPsyZ96OoTJ5RqtKKoApnhRMamau0fWydH1yeOEJd+TRHhc
    45  XStGfhz8QNa1dVFvENczja1vAoGABGWhsd4VPVpHMc7lUvrf4kgKQtTC2PjA4xoc
    46  QJ96hf/642sVE76jl+N6tkGMzGjnVm4P2j+bOy1VvwQavKGoXqJBRd5Apppv727g
    47  /SM7hBXKFc/zH80xKBBgP/i1DR7kdjakCoeu4ngeGywvu2jTS6mQsqzkK+yWbUxJ
    48  I7mYBsECgYB/KNXlTEpXtz/kwWCHFSYA8U74l7zZbVD8ul0e56JDK+lLcJ0tJffk
    49  gqnBycHj6AhEycjda75cs+0zybZvN4x65KZHOGW/O/7OAWEcZP5TPb3zf9ned3Hl
    50  NsZoFj52ponUM6+99A2CmezFCN16c4mbA//luWF+k3VVqR6BpkrhKw==
    51  -----END RSA PRIVATE KEY-----`
    52  
    53  var serverConfig = &ssh.ServerConfig{
    54  	PasswordCallback:  acceptUserPass("user", "pass"),
    55  	PublicKeyCallback: acceptPublicKey(testClientPublicKey),
    56  }
    57  
    58  func init() {
    59  	// Parse and set the private key of the server, required to accept connections
    60  	signer, err := ssh.ParsePrivateKey([]byte(testServerPrivateKey))
    61  	if err != nil {
    62  		panic("unable to parse private key: " + err.Error())
    63  	}
    64  	serverConfig.AddHostKey(signer)
    65  }
    66  
    67  func newMockLineServer(t *testing.T) string {
    68  	l, err := net.Listen("tcp", "127.0.0.1:0")
    69  	if err != nil {
    70  		t.Fatalf("Unable to listen for connection: %s", err)
    71  	}
    72  
    73  	go func() {
    74  		defer l.Close()
    75  		c, err := l.Accept()
    76  		if err != nil {
    77  			t.Errorf("Unable to accept incoming connection: %s", err)
    78  		}
    79  		defer c.Close()
    80  		conn, chans, _, err := ssh.NewServerConn(c, serverConfig)
    81  		if err != nil {
    82  			t.Logf("Handshaking error: %v", err)
    83  		}
    84  		t.Log("Accepted SSH connection")
    85  
    86  		for newChannel := range chans {
    87  			channel, requests, err := newChannel.Accept()
    88  			if err != nil {
    89  				t.Errorf("Unable to accept channel.")
    90  			}
    91  			t.Log("Accepted channel")
    92  
    93  			go func(in <-chan *ssh.Request) {
    94  				for req := range in {
    95  					if req.WantReply {
    96  						req.Reply(true, nil)
    97  					}
    98  				}
    99  			}(requests)
   100  
   101  			go func(newChannel ssh.NewChannel) {
   102  				conn.OpenChannel(newChannel.ChannelType(), nil)
   103  			}(newChannel)
   104  
   105  			defer channel.Close()
   106  		}
   107  		conn.Close()
   108  	}()
   109  
   110  	return l.Addr().String()
   111  }
   112  
   113  func TestNew_Invalid(t *testing.T) {
   114  	address := newMockLineServer(t)
   115  	parts := strings.Split(address, ":")
   116  
   117  	r := &terraform.InstanceState{
   118  		Ephemeral: terraform.EphemeralState{
   119  			ConnInfo: map[string]string{
   120  				"type":     "ssh",
   121  				"user":     "user",
   122  				"password": "i-am-invalid",
   123  				"host":     parts[0],
   124  				"port":     parts[1],
   125  				"timeout":  "30s",
   126  			},
   127  		},
   128  	}
   129  
   130  	c, err := New(r)
   131  	if err != nil {
   132  		t.Fatalf("error creating communicator: %s", err)
   133  	}
   134  
   135  	err = c.Connect(nil)
   136  	if err == nil {
   137  		t.Fatal("should have had an error connecting")
   138  	}
   139  }
   140  
   141  func TestStart(t *testing.T) {
   142  	address := newMockLineServer(t)
   143  	parts := strings.Split(address, ":")
   144  
   145  	r := &terraform.InstanceState{
   146  		Ephemeral: terraform.EphemeralState{
   147  			ConnInfo: map[string]string{
   148  				"type":     "ssh",
   149  				"user":     "user",
   150  				"password": "pass",
   151  				"host":     parts[0],
   152  				"port":     parts[1],
   153  				"timeout":  "30s",
   154  			},
   155  		},
   156  	}
   157  
   158  	c, err := New(r)
   159  	if err != nil {
   160  		t.Fatalf("error creating communicator: %s", err)
   161  	}
   162  
   163  	var cmd remote.Cmd
   164  	stdout := new(bytes.Buffer)
   165  	cmd.Command = "echo foo"
   166  	cmd.Stdout = stdout
   167  
   168  	err = c.Start(&cmd)
   169  	if err != nil {
   170  		t.Fatalf("error executing remote command: %s", err)
   171  	}
   172  }
   173  
   174  func TestAccUploadFile(t *testing.T) {
   175  	// use the local ssh server and scp binary to check uploads
   176  	if ok := os.Getenv("SSH_UPLOAD_TEST"); ok == "" {
   177  		t.Log("Skipping Upload Acceptance without SSH_UPLOAD_TEST set")
   178  		t.Skip()
   179  	}
   180  
   181  	r := &terraform.InstanceState{
   182  		Ephemeral: terraform.EphemeralState{
   183  			ConnInfo: map[string]string{
   184  				"type":    "ssh",
   185  				"user":    os.Getenv("USER"),
   186  				"host":    "127.0.0.1",
   187  				"port":    "22",
   188  				"timeout": "30s",
   189  			},
   190  		},
   191  	}
   192  
   193  	c, err := New(r)
   194  	if err != nil {
   195  		t.Fatalf("error creating communicator: %s", err)
   196  	}
   197  
   198  	tmpDir, err := ioutil.TempDir("", "communicator")
   199  	if err != nil {
   200  		t.Fatal(err)
   201  	}
   202  	defer os.RemoveAll(tmpDir)
   203  
   204  	content := []byte("this is the file content")
   205  	source := bytes.NewReader(content)
   206  	tmpFile := filepath.Join(tmpDir, "tempFile.out")
   207  	err = c.Upload(tmpFile, source)
   208  	if err != nil {
   209  		t.Fatalf("error uploading file: %s", err)
   210  	}
   211  
   212  	data, err := ioutil.ReadFile(tmpFile)
   213  	if err != nil {
   214  		t.Fatal(err)
   215  	}
   216  
   217  	if !bytes.Equal(data, content) {
   218  		t.Fatalf("bad: %s", data)
   219  	}
   220  }
   221  
   222  func TestAccHugeUploadFile(t *testing.T) {
   223  	// use the local ssh server and scp binary to check uploads
   224  	if ok := os.Getenv("SSH_UPLOAD_TEST"); ok == "" {
   225  		t.Log("Skipping Upload Acceptance without SSH_UPLOAD_TEST set")
   226  		t.Skip()
   227  	}
   228  
   229  	r := &terraform.InstanceState{
   230  		Ephemeral: terraform.EphemeralState{
   231  			ConnInfo: map[string]string{
   232  				"type":    "ssh",
   233  				"user":    os.Getenv("USER"),
   234  				"host":    "127.0.0.1",
   235  				"port":    "22",
   236  				"timeout": "30s",
   237  			},
   238  		},
   239  	}
   240  
   241  	c, err := New(r)
   242  	if err != nil {
   243  		t.Fatalf("error creating communicator: %s", err)
   244  	}
   245  
   246  	// copy 4GB of data, random to prevent compression.
   247  	size := int64(1 << 32)
   248  	source := io.LimitReader(rand.New(rand.NewSource(0)), size)
   249  
   250  	dest, err := ioutil.TempFile("", "communicator")
   251  	if err != nil {
   252  		t.Fatal(err)
   253  	}
   254  	destName := dest.Name()
   255  	dest.Close()
   256  	defer os.Remove(destName)
   257  
   258  	t.Log("Uploading to", destName)
   259  
   260  	// bypass the Upload method so we can directly supply the file size
   261  	// preventing the extra copy of the huge file.
   262  	targetDir := filepath.Dir(destName)
   263  	targetFile := filepath.Base(destName)
   264  
   265  	scpFunc := func(w io.Writer, stdoutR *bufio.Reader) error {
   266  		return scpUploadFile(targetFile, source, w, stdoutR, size)
   267  	}
   268  
   269  	err = c.scpSession("scp -vt "+targetDir, scpFunc)
   270  	if err != nil {
   271  		t.Fatal(err)
   272  	}
   273  
   274  	// check the final file size
   275  	fs, err := os.Stat(destName)
   276  	if err != nil {
   277  		t.Fatal(err)
   278  	}
   279  
   280  	if fs.Size() != size {
   281  		t.Fatalf("expected file size of %d, got %d", size, fs.Size())
   282  	}
   283  }
   284  
   285  func TestScriptPath(t *testing.T) {
   286  	cases := []struct {
   287  		Input   string
   288  		Pattern string
   289  	}{
   290  		{
   291  			"/tmp/script.sh",
   292  			`^/tmp/script\.sh$`,
   293  		},
   294  		{
   295  			"/tmp/script_%RAND%.sh",
   296  			`^/tmp/script_(\d+)\.sh$`,
   297  		},
   298  	}
   299  
   300  	for _, tc := range cases {
   301  		r := &terraform.InstanceState{
   302  			Ephemeral: terraform.EphemeralState{
   303  				ConnInfo: map[string]string{
   304  					"type":        "ssh",
   305  					"script_path": tc.Input,
   306  				},
   307  			},
   308  		}
   309  		comm, err := New(r)
   310  		if err != nil {
   311  			t.Fatalf("err: %s", err)
   312  		}
   313  		output := comm.ScriptPath()
   314  
   315  		match, err := regexp.Match(tc.Pattern, []byte(output))
   316  		if err != nil {
   317  			t.Fatalf("bad: %s\n\nerr: %s", tc.Input, err)
   318  		}
   319  		if !match {
   320  			t.Fatalf("bad: %s\n\n%s", tc.Input, output)
   321  		}
   322  	}
   323  }
   324  
   325  func TestScriptPath_randSeed(t *testing.T) {
   326  	// Pre GH-4186 fix, this value was the deterministic start the pseudorandom
   327  	// chain of unseeded math/rand values for Int31().
   328  	staticSeedPath := "/tmp/terraform_1298498081.sh"
   329  	c, err := New(&terraform.InstanceState{})
   330  	if err != nil {
   331  		t.Fatalf("err: %s", err)
   332  	}
   333  	path := c.ScriptPath()
   334  	if path == staticSeedPath {
   335  		t.Fatalf("rand not seeded! got: %s", path)
   336  	}
   337  }
   338  
   339  const testClientPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
   340  MIIEpQIBAAKCAQEAxOgNXOJ/jrRDxBZTSk2X9otNy9zcpUmJr5ifDi5sy7j2ZiQS
   341  beBt1Wf+tLNWis8Cyq06ttEvjjRuM75yucyD6GrqDTXVCSm4PeOIQeDhPhw26wYZ
   342  O0h/mFgrAiCwaEl8AFXVBInOhVn/0nqgUpkwckh76bjTsNeifkiugK3cfJOuBdrU
   343  ZGbgugJjmYo4Cmv7ECo1gCFT5N+BAjOji3z3N5ClBH5HaWC77jH7kTH0k5cZ+ZRQ
   344  tG9EqLyvWlnzTAR/Yly0JInkOa16Ms1Au5sIJwEoJfHKsNVK06IlLob53nblwYW0
   345  H5gv1Kb/rS+nUkpPtA5YFShB7iZnPLPPv6qXSwIDAQABAoIBAC0UY1rMkB9/rbQK
   346  2G6+bPgI1HrDydAdkeQdsOxyPH43jlG8GGwHYZ3l/S4pkLqewijcmACay6Rm5IP8
   347  Kg/XfquLLqJvnKJIZuHkYaGTdn3dv8T21Hf6FRwvs0j9auW1TSpWfDpZwmpNPIBX
   348  irTeVXUUmynbIrvt4km/IhRbuYrbbb964CLYD1DCl3XssXxoRNvPpc5EtOuyDorA
   349  5g1hvZR1FqbOAmOuNQMYJociMuWB8mCaHb+o1Sg4A65OLXxoKs0cuwInJ/n/R4Z3
   350  +GrV+x5ypBMxXgjjQtKMLEOujkvxs1cp34hkbhKMHHXxbMu5jl74YtGGsLLk90rq
   351  ieZGIgECgYEA49OM9mMCrDoFUTZdJaSARA/MOXkdQgrqVTv9kUHee7oeMZZ6lS0i
   352  bPU7g+Bq+UAN0qcw9x992eAElKjBA71Q5UbZYWh29BDMZd8bRJmwz4P6aSMoYLWI
   353  Sr31caJU9LdmPFatarNeehjSJtlTuoZD9+NElnnUwNaTeOOo5UdhTQsCgYEA3UGm
   354  QWoDUttFwK9oL2KL8M54Bx6EzNhnyk03WrqBbR7PJcPKnsF0R/0soQ+y0FW0r8RJ
   355  TqG6ze5fUJII72B4GlMTQdP+BIvaKQttwWQTNIjbbv4NksF445gdVOO1xi9SvQ7k
   356  uvMVxOb+1jL3HAFa3furWu2tJRDs6dhuaILLxsECgYEAhnhlKUBDYZhVbxvhWsh/
   357  lKymY/3ikQqUSX7BKa1xPiIalDY3YDllql4MpMgfG8L85asdMZ96ztB0o7H/Ss/B
   358  IbLxt5bLLz+DBVXsaE82lyVU9h10RbCgI01/w3SHJHHjfBXFAcehKfvgfmGkE+IP
   359  2A5ie1aphrCgFqh5FetNuQUCgYEAibL42I804FUtFR1VduAa/dRRqQSaW6528dWa
   360  lLGsKRBalUNEEAeP6dmr89UEUVp1qEo94V0QGGe5FDi+rNPaC3AWdQqNdaDgNlkx
   361  hoFU3oYqIuqj4ejc5rBd2N4a2+vJz3W8bokozDGC+iYf2mMRfUPKwj1XW9Er0OFs
   362  3UhBsEECgYEAto/iJB7ZlCM7EyV9JW0tsEt83rbKMQ/Ex0ShbBIejej0Xx7bwx60
   363  tVgay+bzJnNkXu6J4XVI98A/WsdI2kW4hL0STYdHV5HVA1l87V4ZbvTF2Bx8a8RJ
   364  OF3UjpMTWKqOprw9nAu5VuwNRVzORF8ER8rgGeaR2/gsSvIYFy9VXq8=
   365  -----END RSA PRIVATE KEY-----`
   366  
   367  var testClientPublicKey = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDE6A1c4n+OtEPEFlNKTZf2i03L3NylSYmvmJ8OLmzLuPZmJBJt4G3VZ/60s1aKzwLKrTq20S+ONG4zvnK5zIPoauoNNdUJKbg944hB4OE+HDbrBhk7SH+YWCsCILBoSXwAVdUEic6FWf/SeqBSmTBySHvpuNOw16J+SK6Ardx8k64F2tRkZuC6AmOZijgKa/sQKjWAIVPk34ECM6OLfPc3kKUEfkdpYLvuMfuRMfSTlxn5lFC0b0SovK9aWfNMBH9iXLQkieQ5rXoyzUC7mwgnASgl8cqw1UrToiUuhvneduXBhbQfmC/Upv+tL6dSSk+0DlgVKEHuJmc8s8+/qpdL`
   368  
   369  func acceptUserPass(goodUser, goodPass string) func(ssh.ConnMetadata, []byte) (*ssh.Permissions, error) {
   370  	return func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
   371  		if c.User() == goodUser && string(pass) == goodPass {
   372  			return nil, nil
   373  		}
   374  		return nil, fmt.Errorf("password rejected for %q", c.User())
   375  	}
   376  }
   377  
   378  func acceptPublicKey(keystr string) func(ssh.ConnMetadata, ssh.PublicKey) (*ssh.Permissions, error) {
   379  	goodkey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(keystr))
   380  	if err != nil {
   381  		panic(fmt.Errorf("error parsing key: %s", err))
   382  	}
   383  	return func(_ ssh.ConnMetadata, inkey ssh.PublicKey) (*ssh.Permissions, error) {
   384  		if bytes.Compare(inkey.Marshal(), goodkey.Marshal()) == 0 {
   385  			return nil, nil
   386  		}
   387  
   388  		return nil, fmt.Errorf("public key rejected")
   389  	}
   390  }