github.com/dtx/terraform@v0.7.4-0.20160907225157-926acfd0821e/communicator/ssh/communicator_test.go (about)

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