github.com/turtlemonvh/terraform@v0.6.9-0.20151204001754-8e40b6b855e8/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 comm := &Communicator{connInfo: &connectionInfo{ScriptPath: tc.Input}} 229 output := comm.ScriptPath() 230 231 match, err := regexp.Match(tc.Pattern, []byte(output)) 232 if err != nil { 233 t.Fatalf("bad: %s\n\nerr: %s", tc.Input, err) 234 } 235 if !match { 236 t.Fatalf("bad: %s\n\n%s", tc.Input, output) 237 } 238 } 239 } 240 241 const testClientPrivateKey = `-----BEGIN RSA PRIVATE KEY----- 242 MIIEpQIBAAKCAQEAxOgNXOJ/jrRDxBZTSk2X9otNy9zcpUmJr5ifDi5sy7j2ZiQS 243 beBt1Wf+tLNWis8Cyq06ttEvjjRuM75yucyD6GrqDTXVCSm4PeOIQeDhPhw26wYZ 244 O0h/mFgrAiCwaEl8AFXVBInOhVn/0nqgUpkwckh76bjTsNeifkiugK3cfJOuBdrU 245 ZGbgugJjmYo4Cmv7ECo1gCFT5N+BAjOji3z3N5ClBH5HaWC77jH7kTH0k5cZ+ZRQ 246 tG9EqLyvWlnzTAR/Yly0JInkOa16Ms1Au5sIJwEoJfHKsNVK06IlLob53nblwYW0 247 H5gv1Kb/rS+nUkpPtA5YFShB7iZnPLPPv6qXSwIDAQABAoIBAC0UY1rMkB9/rbQK 248 2G6+bPgI1HrDydAdkeQdsOxyPH43jlG8GGwHYZ3l/S4pkLqewijcmACay6Rm5IP8 249 Kg/XfquLLqJvnKJIZuHkYaGTdn3dv8T21Hf6FRwvs0j9auW1TSpWfDpZwmpNPIBX 250 irTeVXUUmynbIrvt4km/IhRbuYrbbb964CLYD1DCl3XssXxoRNvPpc5EtOuyDorA 251 5g1hvZR1FqbOAmOuNQMYJociMuWB8mCaHb+o1Sg4A65OLXxoKs0cuwInJ/n/R4Z3 252 +GrV+x5ypBMxXgjjQtKMLEOujkvxs1cp34hkbhKMHHXxbMu5jl74YtGGsLLk90rq 253 ieZGIgECgYEA49OM9mMCrDoFUTZdJaSARA/MOXkdQgrqVTv9kUHee7oeMZZ6lS0i 254 bPU7g+Bq+UAN0qcw9x992eAElKjBA71Q5UbZYWh29BDMZd8bRJmwz4P6aSMoYLWI 255 Sr31caJU9LdmPFatarNeehjSJtlTuoZD9+NElnnUwNaTeOOo5UdhTQsCgYEA3UGm 256 QWoDUttFwK9oL2KL8M54Bx6EzNhnyk03WrqBbR7PJcPKnsF0R/0soQ+y0FW0r8RJ 257 TqG6ze5fUJII72B4GlMTQdP+BIvaKQttwWQTNIjbbv4NksF445gdVOO1xi9SvQ7k 258 uvMVxOb+1jL3HAFa3furWu2tJRDs6dhuaILLxsECgYEAhnhlKUBDYZhVbxvhWsh/ 259 lKymY/3ikQqUSX7BKa1xPiIalDY3YDllql4MpMgfG8L85asdMZ96ztB0o7H/Ss/B 260 IbLxt5bLLz+DBVXsaE82lyVU9h10RbCgI01/w3SHJHHjfBXFAcehKfvgfmGkE+IP 261 2A5ie1aphrCgFqh5FetNuQUCgYEAibL42I804FUtFR1VduAa/dRRqQSaW6528dWa 262 lLGsKRBalUNEEAeP6dmr89UEUVp1qEo94V0QGGe5FDi+rNPaC3AWdQqNdaDgNlkx 263 hoFU3oYqIuqj4ejc5rBd2N4a2+vJz3W8bokozDGC+iYf2mMRfUPKwj1XW9Er0OFs 264 3UhBsEECgYEAto/iJB7ZlCM7EyV9JW0tsEt83rbKMQ/Ex0ShbBIejej0Xx7bwx60 265 tVgay+bzJnNkXu6J4XVI98A/WsdI2kW4hL0STYdHV5HVA1l87V4ZbvTF2Bx8a8RJ 266 OF3UjpMTWKqOprw9nAu5VuwNRVzORF8ER8rgGeaR2/gsSvIYFy9VXq8= 267 -----END RSA PRIVATE KEY-----` 268 269 var testClientPublicKey = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDE6A1c4n+OtEPEFlNKTZf2i03L3NylSYmvmJ8OLmzLuPZmJBJt4G3VZ/60s1aKzwLKrTq20S+ONG4zvnK5zIPoauoNNdUJKbg944hB4OE+HDbrBhk7SH+YWCsCILBoSXwAVdUEic6FWf/SeqBSmTBySHvpuNOw16J+SK6Ardx8k64F2tRkZuC6AmOZijgKa/sQKjWAIVPk34ECM6OLfPc3kKUEfkdpYLvuMfuRMfSTlxn5lFC0b0SovK9aWfNMBH9iXLQkieQ5rXoyzUC7mwgnASgl8cqw1UrToiUuhvneduXBhbQfmC/Upv+tL6dSSk+0DlgVKEHuJmc8s8+/qpdL` 270 271 func acceptUserPass(goodUser, goodPass string) func(ssh.ConnMetadata, []byte) (*ssh.Permissions, error) { 272 return func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) { 273 if c.User() == goodUser && string(pass) == goodPass { 274 return nil, nil 275 } 276 return nil, fmt.Errorf("password rejected for %q", c.User()) 277 } 278 } 279 280 func acceptPublicKey(keystr string) func(ssh.ConnMetadata, ssh.PublicKey) (*ssh.Permissions, error) { 281 goodkey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(keystr)) 282 if err != nil { 283 panic(fmt.Errorf("error parsing key: %s", err)) 284 } 285 return func(_ ssh.ConnMetadata, inkey ssh.PublicKey) (*ssh.Permissions, error) { 286 if bytes.Compare(inkey.Marshal(), goodkey.Marshal()) == 0 { 287 return nil, nil 288 } 289 290 return nil, fmt.Errorf("public key rejected") 291 } 292 }