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