github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/communicator/ssh/communicator_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 //go:build !race 5 // +build !race 6 7 package ssh 8 9 import ( 10 "bufio" 11 "bytes" 12 "encoding/base64" 13 "fmt" 14 "io" 15 "io/ioutil" 16 "math/rand" 17 "net" 18 "os" 19 "path/filepath" 20 "regexp" 21 "strconv" 22 "strings" 23 "testing" 24 "time" 25 26 "github.com/terramate-io/tf/communicator/remote" 27 "github.com/zclconf/go-cty/cty" 28 "golang.org/x/crypto/ssh" 29 ) 30 31 // private key for mock server 32 const testServerPrivateKey = `-----BEGIN RSA PRIVATE KEY----- 33 MIIEpAIBAAKCAQEA19lGVsTqIT5iiNYRgnoY1CwkbETW5cq+Rzk5v/kTlf31XpSU 34 70HVWkbTERECjaYdXM2gGcbb+sxpq6GtXf1M3kVomycqhxwhPv4Cr6Xp4WT/jkFx 35 9z+FFzpeodGJWjOH6L2H5uX1Cvr9EDdQp9t9/J32/qBFntY8GwoUI/y/1MSTmMiF 36 tupdMODN064vd3gyMKTwrlQ8tZM6aYuyOPsutLlUY7M5x5FwMDYvnPDSeyT/Iw0z 37 s3B+NCyqeeMd2T7YzQFnRATj0M7rM5LoSs7DVqVriOEABssFyLj31PboaoLhOKgc 38 qoM9khkNzr7FHVvi+DhYM2jD0DwvqZLN6NmnLwIDAQABAoIBAQCGVj+kuSFOV1lT 39 +IclQYA6bM6uY5mroqcSBNegVxCNhWU03BxlW//BE9tA/+kq53vWylMeN9mpGZea 40 riEMIh25KFGWXqXlOOioH8bkMsqA8S7sBmc7jljyv+0toQ9vCCtJ+sueNPhxQQxH 41 D2YvUjfzBQ04I9+wn30BByDJ1QA/FoPsunxIOUCcRBE/7jxuLYcpR+JvEF68yYIh 42 atXRld4W4in7T65YDR8jK1Uj9XAcNeDYNpT/M6oFLx1aPIlkG86aCWRO19S1jLPT 43 b1ZAKHHxPMCVkSYW0RqvIgLXQOR62D0Zne6/2wtzJkk5UCjkSQ2z7ZzJpMkWgDgN 44 ifCULFPBAoGBAPoMZ5q1w+zB+knXUD33n1J+niN6TZHJulpf2w5zsW+m2K6Zn62M 45 MXndXlVAHtk6p02q9kxHdgov34Uo8VpuNjbS1+abGFTI8NZgFo+bsDxJdItemwC4 46 KJ7L1iz39hRN/ZylMRLz5uTYRGddCkeIHhiG2h7zohH/MaYzUacXEEy3AoGBANz8 47 e/msleB+iXC0cXKwds26N4hyMdAFE5qAqJXvV3S2W8JZnmU+sS7vPAWMYPlERPk1 48 D8Q2eXqdPIkAWBhrx4RxD7rNc5qFNcQWEhCIxC9fccluH1y5g2M+4jpMX2CT8Uv+ 49 3z+NoJ5uDTXZTnLCfoZzgZ4nCZVZ+6iU5U1+YXFJAoGBANLPpIV920n/nJmmquMj 50 orI1R/QXR9Cy56cMC65agezlGOfTYxk5Cfl5Ve+/2IJCfgzwJyjWUsFx7RviEeGw 51 64o7JoUom1HX+5xxdHPsyZ96OoTJ5RqtKKoApnhRMamau0fWydH1yeOEJd+TRHhc 52 XStGfhz8QNa1dVFvENczja1vAoGABGWhsd4VPVpHMc7lUvrf4kgKQtTC2PjA4xoc 53 QJ96hf/642sVE76jl+N6tkGMzGjnVm4P2j+bOy1VvwQavKGoXqJBRd5Apppv727g 54 /SM7hBXKFc/zH80xKBBgP/i1DR7kdjakCoeu4ngeGywvu2jTS6mQsqzkK+yWbUxJ 55 I7mYBsECgYB/KNXlTEpXtz/kwWCHFSYA8U74l7zZbVD8ul0e56JDK+lLcJ0tJffk 56 gqnBycHj6AhEycjda75cs+0zybZvN4x65KZHOGW/O/7OAWEcZP5TPb3zf9ned3Hl 57 NsZoFj52ponUM6+99A2CmezFCN16c4mbA//luWF+k3VVqR6BpkrhKw== 58 -----END RSA PRIVATE KEY-----` 59 60 // this cert was signed by the key from testCAPublicKey 61 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=` 62 63 const testCAPublicKey = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCrozyZIhdEvalCn+eSzHH94cO9ykiywA13ntWI7mJcHBwYTeCYWG8E9zGXyp2iDOjCGudM0Tdt8o0OofKChk9Z/qiUN0G8y1kmaXBlBM3qA5R9NPpvMYMNkYLfX6ivtZCnqrsbzaoqN2Oc/7H2StHzJWh/XCGu9otQZA6vdv1oSmAsZOjw/xIGaGQqDUaLq21J280PP1qSbdJHf76iSHE+TWe3YpqV946JWM5tCh0DykZ10VznvxYpUjzhr07IN3tVKxOXbPnnU7lX6IaLIWgfzLqwSyheeux05c3JLF9iF4sFu8ou4hwQz1iuUTU1jxgwZP0w/bkXgFFs0949lW81` 64 65 func newMockLineServer(t *testing.T, signer ssh.Signer, pubKey string) string { 66 serverConfig := &ssh.ServerConfig{ 67 PasswordCallback: acceptUserPass("user", "pass"), 68 PublicKeyCallback: acceptPublicKey(pubKey), 69 } 70 71 var err error 72 if signer == nil { 73 signer, err = ssh.ParsePrivateKey([]byte(testServerPrivateKey)) 74 if err != nil { 75 t.Fatalf("unable to parse private key: %s", err) 76 } 77 } 78 serverConfig.AddHostKey(signer) 79 80 l, err := net.Listen("tcp", "127.0.0.1:0") 81 if err != nil { 82 t.Fatalf("Unable to listen for connection: %s", err) 83 } 84 85 go func() { 86 defer l.Close() 87 c, err := l.Accept() 88 if err != nil { 89 t.Errorf("Unable to accept incoming connection: %s", err) 90 } 91 defer c.Close() 92 conn, chans, _, err := ssh.NewServerConn(c, serverConfig) 93 if err != nil { 94 t.Logf("Handshaking error: %v", err) 95 } 96 t.Log("Accepted SSH connection") 97 98 for newChannel := range chans { 99 channel, requests, err := newChannel.Accept() 100 if err != nil { 101 t.Errorf("Unable to accept channel.") 102 } 103 t.Log("Accepted channel") 104 105 go func(in <-chan *ssh.Request) { 106 defer channel.Close() 107 for req := range in { 108 // since this channel's requests are serviced serially, 109 // this will block keepalive probes, and can simulate a 110 // hung connection. 111 if bytes.Contains(req.Payload, []byte("sleep")) { 112 time.Sleep(time.Second) 113 } 114 115 if req.WantReply { 116 req.Reply(true, nil) 117 } 118 } 119 }(requests) 120 } 121 conn.Close() 122 }() 123 124 return l.Addr().String() 125 } 126 127 func TestNew_Invalid(t *testing.T) { 128 address := newMockLineServer(t, nil, testClientPublicKey) 129 parts := strings.Split(address, ":") 130 131 v := cty.ObjectVal(map[string]cty.Value{ 132 "type": cty.StringVal("ssh"), 133 "user": cty.StringVal("user"), 134 "password": cty.StringVal("i-am-invalid"), 135 "host": cty.StringVal(parts[0]), 136 "port": cty.StringVal(parts[1]), 137 "timeout": cty.StringVal("30s"), 138 }) 139 140 c, err := New(v) 141 if err != nil { 142 t.Fatalf("error creating communicator: %s", err) 143 } 144 145 err = c.Connect(nil) 146 if err == nil { 147 t.Fatal("should have had an error connecting") 148 } 149 } 150 151 func TestNew_InvalidHost(t *testing.T) { 152 v := cty.ObjectVal(map[string]cty.Value{ 153 "type": cty.StringVal("ssh"), 154 "user": cty.StringVal("user"), 155 "password": cty.StringVal("i-am-invalid"), 156 "port": cty.StringVal("22"), 157 "timeout": cty.StringVal("30s"), 158 }) 159 160 _, err := New(v) 161 if err == nil { 162 t.Fatal("should have had an error creating communicator") 163 } 164 } 165 166 func TestStart(t *testing.T) { 167 address := newMockLineServer(t, nil, testClientPublicKey) 168 parts := strings.Split(address, ":") 169 170 v := cty.ObjectVal(map[string]cty.Value{ 171 "type": cty.StringVal("ssh"), 172 "user": cty.StringVal("user"), 173 "password": cty.StringVal("pass"), 174 "host": cty.StringVal(parts[0]), 175 "port": cty.StringVal(parts[1]), 176 "timeout": cty.StringVal("30s"), 177 }) 178 179 c, err := New(v) 180 if err != nil { 181 t.Fatalf("error creating communicator: %s", err) 182 } 183 184 var cmd remote.Cmd 185 stdout := new(bytes.Buffer) 186 cmd.Command = "echo foo" 187 cmd.Stdout = stdout 188 189 err = c.Start(&cmd) 190 if err != nil { 191 t.Fatalf("error executing remote command: %s", err) 192 } 193 } 194 195 // TestKeepAlives verifies that the keepalive messages don't interfere with 196 // normal operation of the client. 197 func TestKeepAlives(t *testing.T) { 198 ivl := keepAliveInterval 199 keepAliveInterval = 250 * time.Millisecond 200 defer func() { keepAliveInterval = ivl }() 201 202 address := newMockLineServer(t, nil, testClientPublicKey) 203 parts := strings.Split(address, ":") 204 205 v := cty.ObjectVal(map[string]cty.Value{ 206 "type": cty.StringVal("ssh"), 207 "user": cty.StringVal("user"), 208 "password": cty.StringVal("pass"), 209 "host": cty.StringVal(parts[0]), 210 "port": cty.StringVal(parts[1]), 211 }) 212 213 c, err := New(v) 214 if err != nil { 215 t.Fatalf("error creating communicator: %s", err) 216 } 217 218 if err := c.Connect(nil); err != nil { 219 t.Fatal(err) 220 } 221 222 var cmd remote.Cmd 223 stdout := new(bytes.Buffer) 224 cmd.Command = "sleep" 225 cmd.Stdout = stdout 226 227 // wait a bit before executing the command, so that at least 1 keepalive is sent 228 time.Sleep(500 * time.Millisecond) 229 230 err = c.Start(&cmd) 231 if err != nil { 232 t.Fatalf("error executing remote command: %s", err) 233 } 234 } 235 236 // TestDeadConnection verifies that failed keepalive messages will eventually 237 // kill the connection. 238 func TestFailedKeepAlives(t *testing.T) { 239 ivl := keepAliveInterval 240 del := maxKeepAliveDelay 241 maxKeepAliveDelay = 500 * time.Millisecond 242 keepAliveInterval = 250 * time.Millisecond 243 defer func() { 244 keepAliveInterval = ivl 245 maxKeepAliveDelay = del 246 }() 247 248 address := newMockLineServer(t, nil, testClientPublicKey) 249 parts := strings.Split(address, ":") 250 251 v := cty.ObjectVal(map[string]cty.Value{ 252 "type": cty.StringVal("ssh"), 253 "user": cty.StringVal("user"), 254 "password": cty.StringVal("pass"), 255 "host": cty.StringVal(parts[0]), 256 "port": cty.StringVal(parts[1]), 257 "timeout": cty.StringVal("30s"), 258 }) 259 260 c, err := New(v) 261 if err != nil { 262 t.Fatalf("error creating communicator: %s", err) 263 } 264 265 if err := c.Connect(nil); err != nil { 266 t.Fatal(err) 267 } 268 var cmd remote.Cmd 269 stdout := new(bytes.Buffer) 270 cmd.Command = "sleep" 271 cmd.Stdout = stdout 272 273 err = c.Start(&cmd) 274 if err == nil { 275 t.Fatal("expected connection error") 276 } 277 } 278 279 func TestLostConnection(t *testing.T) { 280 address := newMockLineServer(t, nil, testClientPublicKey) 281 parts := strings.Split(address, ":") 282 283 v := cty.ObjectVal(map[string]cty.Value{ 284 "type": cty.StringVal("ssh"), 285 "user": cty.StringVal("user"), 286 "password": cty.StringVal("pass"), 287 "host": cty.StringVal(parts[0]), 288 "port": cty.StringVal(parts[1]), 289 "timeout": cty.StringVal("30s"), 290 }) 291 292 c, err := New(v) 293 if err != nil { 294 t.Fatalf("error creating communicator: %s", err) 295 } 296 297 var cmd remote.Cmd 298 stdout := new(bytes.Buffer) 299 cmd.Command = "echo foo" 300 cmd.Stdout = stdout 301 302 err = c.Start(&cmd) 303 if err != nil { 304 t.Fatalf("error executing remote command: %s", err) 305 } 306 307 // The test server can't execute anything, so Wait will block, unless 308 // there's an error. Disconnect the communicator transport, to cause the 309 // command to fail. 310 go func() { 311 time.Sleep(100 * time.Millisecond) 312 c.Disconnect() 313 }() 314 315 err = cmd.Wait() 316 if err == nil { 317 t.Fatal("expected communicator error") 318 } 319 } 320 321 func TestHostKey(t *testing.T) { 322 // get the server's public key 323 signer, err := ssh.ParsePrivateKey([]byte(testServerPrivateKey)) 324 if err != nil { 325 t.Fatalf("unable to parse private key: %v", err) 326 } 327 pubKey := fmt.Sprintf("ssh-rsa %s", base64.StdEncoding.EncodeToString(signer.PublicKey().Marshal())) 328 329 address := newMockLineServer(t, nil, testClientPublicKey) 330 host, p, _ := net.SplitHostPort(address) 331 port, _ := strconv.Atoi(p) 332 333 connInfo := &connectionInfo{ 334 User: "user", 335 Password: "pass", 336 Host: host, 337 HostKey: pubKey, 338 Port: uint16(port), 339 Timeout: "30s", 340 } 341 342 cfg, err := prepareSSHConfig(connInfo) 343 if err != nil { 344 t.Fatal(err) 345 } 346 347 c := &Communicator{ 348 connInfo: connInfo, 349 config: cfg, 350 } 351 352 var cmd remote.Cmd 353 stdout := new(bytes.Buffer) 354 cmd.Command = "echo foo" 355 cmd.Stdout = stdout 356 357 if err := c.Start(&cmd); err != nil { 358 t.Fatal(err) 359 } 360 if err := c.Disconnect(); err != nil { 361 t.Fatal(err) 362 } 363 364 // now check with the wrong HostKey 365 address = newMockLineServer(t, nil, testClientPublicKey) 366 _, p, _ = net.SplitHostPort(address) 367 port, _ = strconv.Atoi(p) 368 369 connInfo.HostKey = testClientPublicKey 370 connInfo.Port = uint16(port) 371 372 cfg, err = prepareSSHConfig(connInfo) 373 if err != nil { 374 t.Fatal(err) 375 } 376 377 c = &Communicator{ 378 connInfo: connInfo, 379 config: cfg, 380 } 381 382 err = c.Start(&cmd) 383 if err == nil || !strings.Contains(err.Error(), "mismatch") { 384 t.Fatalf("expected host key mismatch, got error:%v", err) 385 } 386 } 387 388 func TestHostCert(t *testing.T) { 389 pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(testServerHostCert)) 390 if err != nil { 391 t.Fatal(err) 392 } 393 394 signer, err := ssh.ParsePrivateKey([]byte(testServerPrivateKey)) 395 if err != nil { 396 t.Fatal(err) 397 } 398 399 signer, err = ssh.NewCertSigner(pk.(*ssh.Certificate), signer) 400 if err != nil { 401 t.Fatal(err) 402 } 403 404 address := newMockLineServer(t, signer, testClientPublicKey) 405 host, p, _ := net.SplitHostPort(address) 406 port, _ := strconv.Atoi(p) 407 408 connInfo := &connectionInfo{ 409 User: "user", 410 Password: "pass", 411 Host: host, 412 HostKey: testCAPublicKey, 413 Port: uint16(port), 414 Timeout: "30s", 415 } 416 417 cfg, err := prepareSSHConfig(connInfo) 418 if err != nil { 419 t.Fatal(err) 420 } 421 422 c := &Communicator{ 423 connInfo: connInfo, 424 config: cfg, 425 } 426 427 var cmd remote.Cmd 428 stdout := new(bytes.Buffer) 429 cmd.Command = "echo foo" 430 cmd.Stdout = stdout 431 432 if err := c.Start(&cmd); err != nil { 433 t.Fatal(err) 434 } 435 if err := c.Disconnect(); err != nil { 436 t.Fatal(err) 437 } 438 439 // now check with the wrong HostKey 440 address = newMockLineServer(t, signer, testClientPublicKey) 441 _, p, _ = net.SplitHostPort(address) 442 port, _ = strconv.Atoi(p) 443 444 connInfo.HostKey = testClientPublicKey 445 connInfo.Port = uint16(port) 446 447 cfg, err = prepareSSHConfig(connInfo) 448 if err != nil { 449 t.Fatal(err) 450 } 451 452 c = &Communicator{ 453 connInfo: connInfo, 454 config: cfg, 455 } 456 457 err = c.Start(&cmd) 458 if err == nil || !strings.Contains(err.Error(), "authorities") { 459 t.Fatalf("expected host key mismatch, got error:%v", err) 460 } 461 } 462 463 const SERVER_PEM = `-----BEGIN RSA PRIVATE KEY----- 464 MIIEpAIBAAKCAQEA8CkDr7uxCFt6lQUVwS8NyPO+fQNxORoGnMnN/XhVJZvpqyKR 465 Uji9R0d8D66bYxUUsabXjP2y4HTVzbZtnvXFZZshk0cOtJjjekpYJaLK2esPR/iX 466 wvSltNkrDQDPN/RmgEEMIevW8AgrPsqrnybFHxTpd7rEUHXBOe4nMNRIg3XHykB6 467 jZk8q5bBPUe3I/f0DK5TJEBpTc6dO3P/j93u55VUqr39/SPRHnld2mCw+c8v6UOh 468 sssO/DIZFPScD3DYqsk2N+/nz9zXfcOTdWGhawgxuIo1DTokrNQbG3pDrLqcWgqj 469 13vqJFCmRA0O2CQIwJePd6+Np/XO3Uh/KL6FlQIDAQABAoIBAQCmvQMXNmvCDqk7 470 30zsVDvw4fHGH+azK3Od1aqTqcEMHISOUbCtckFPxLzIsoSltRQqB1kuRVG07skm 471 Stsu+xny4lLcSwBVuLRuykEK2EyYIc/5Owo6y9pkhkaSf5ZfFes4bnD6+B/BhRpp 472 PRMMq0E+xCkX/G6iIi9mhgdlqm0x/vKtjzQeeshw9+gRcRLUpX+UeKFKXMXcDayx 473 qekr1bAaQKNBhTK+CbZjcqzG4f+BXVGRTZ9nsPAV+yTnWUCU0TghwPmtthHbebqa 474 9hlkum7qik/bQj/tjJ8/b0vTfHQSVxhtPG/ZV2Tn9ZuL/vrkYqeyMU8XkJ/uaEvH 475 WPyOcB4BAoGBAP5o5JSEtPog+U3JFrLNSRjz5ofZNVkJzice+0XyqlzJDHhX5tF8 476 mriYQZLLXYhckBm4IdkhTn/dVbXNQTzyy2WVuO5nU8bkCMvGL9CGpW4YGqwGf7NX 477 e4H3emtRjLv8VZpUHe/RUUDhmYvMSt1qmXuskfpROuGfLhQBUd6A4J+BAoGBAPGp 478 UcMKjrxZ5qjYU6DLgS+xeca4Eu70HgdbSQbRo45WubXjyXvTRFij36DrpxJWf1D7 479 lIsyBifoTra/lAuC1NQXGYWjTCdk2ey8Ll5qOgiXvE6lINHABr+U/Z90/g6LuML2 480 VzaZbq/QLcT3yVsdyTogKckzCaKsCpusyHE1CXAVAoGAd6kMglKc8N0bhZukgnsN 481 +5+UeacPcY6sGTh4RWErAjNKGzx1A2lROKvcg9gFaULoQECcIw2IZ5nKW5VsLueg 482 BWrTrcaJ4A2XmYjhKnp6SvspaGoyHD90hx/Iw7t6r1yzQsB3yDmytwqldtyjBdvC 483 zynPC2azhDWjraMlR7tka4ECgYAxwvLiHa9sm3qCtCDsUFtmrb3srITBjaUNUL/F 484 1q8+JR+Sk7gudj9xnTT0VvINNaB71YIt83wPBagHu4VJpYQbtDH+MbUBu6OgOtO1 485 f1w53rzY2OncJxV8p7pd9mJGLoE6LC2jQY7oRw7Vq0xcJdME1BCmrIrEY3a/vaF8 486 pjYuTQKBgQCIOH23Xita8KmhH0NdlWxZfcQt1j3AnOcKe6UyN4BsF8hqS7eTA52s 487 WjG5X2IBl7gs1eMM1qkqR8npS9nwfO/pBmZPwjiZoilypXxWj+c+P3vwre2yija4 488 bXgFVj4KFBwhr1+8KcobxC0SAPEouMvSkxzjjw+gnebozUtPlud9jA== 489 -----END RSA PRIVATE KEY----- 490 ` 491 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` 492 const CLIENT_PEM = `-----BEGIN RSA PRIVATE KEY----- 493 MIIEpAIBAAKCAQEAsruexO8yFkchFrmojoopDkmShY28C5KRPX26BXyy7DVCY+M8 494 PuW2VCbc1ivK6D44C/LNaD53K6OcLk7SNjbuH5KXhStB2996OhMpLLo+DpuPulHp 495 p9UBHG9G2aivGcoPQ4PXXj+y3hNL+/G+f42WnxYMMb/v7DITND0Q9+DQg2HGJpJB 496 5b0LUFhQHWz1woIITm3nWcmHaKmVboU7IjzFrUEv3HPTOLb+pjzrkJYWbd0fzof5 497 MFMruOaBkW+W4TuCYnKMxUt6HgpaVPlDG4LJZMwKSasWQVje/jwxUmYSpNhrJcns 498 j+LB9xN9GdNMlYv5UGIJkLAfh4OgpXpjUDDbvwIDAQABAoIBAEu2ctFVyk/pnbi0 499 uRR4rl+hBvKQUeJNGj2ELvL4Ggs5nIAX2IOEZ7JKLC6FqpSrFq7pEd5g57aSvixX 500 s3DH4CN7w7fj1ShBCNPlHgIWewdRGpeA74vrDWdwNAEsFdDE6aZeCTOhpDGy1vNJ 501 OrtpzS5i9pN0jTvvEneEjtWSZIHiiVlN+0hsFaiwZ6KXON+sDccZPmnP6Fzwj5Rc 502 WS0dKSwnxnx0otWgwWFs8nr306nSeMsNmQkHsS9lz4DEVpp9owdzrX1JmbQvNYAV 503 ohmB3ET4JYFgerqPXJfed9poueGuWCP6MYhsjNeHN35QhofxdO5/0i3JlZfqwZei 504 tNq/0oECgYEA6SqjRqDiIp3ajwyB7Wf0cIQG/P6JZDyN1jl//htgniliIH5UP1Tm 505 uAMG5MincV6X9lOyXyh6Yofu5+NR0yt9SqbDZVJ3ZCxKTun7pxJvQFd7wl5bMkiJ 506 qVfS08k6gQHHDoO+eel+DtpIfWc+e3tvX0aihSU0GZEMqDXYkkphLGECgYEAxDxb 507 +JwJ3N5UEjjkuvFBpuJnmjIaN9HvQkTv3inlx1gLE4iWBZXXsu4aWF8MCUeAAZyP 508 42hQDSkCYX/A22tYCEn/jfrU6A+6rkWBTjdUlYLvlSkhosSnO+117WEItb5cUE95 509 hF4UY7LNs1AsDkV4WE87f/EjpxSwUAjB2Lfd/B8CgYAJ/JiHsuZcozQ0Qk3iVDyF 510 ATKnbWOHFozgqw/PW27U92LLj32eRM2o/gAylmGNmoaZt1YBe2NaiwXxiqv7hnZU 511 VzYxRcn1UWxRWvY7Xq/DKrwTRCVVzwOObEOMbKcD1YaoGX50DEso6bKHJH/pnAzW 512 INlfKIvFuI+5OK0w/tyQoQKBgQCf/jpaOxaLfrV62eobRQJrByLDBGB97GsvU7di 513 IjTWz8DQH0d5rE7d8uWF8ZCFrEcAiV6DYZQK9smbJqbd/uoacAKtBro5rkFdPwwK 514 8m/DKqsdqRhkdgOHh7bjYH7Sdy8ax4Fi27WyB6FQtmgFBrz0+zyetsODwQlzZ4Bs 515 qpSRrwKBgQC0vWHrY5aGIdF+b8EpP0/SSLLALpMySHyWhDyxYcPqdhszYbjDcavv 516 xrrLXNUD2duBHKPVYE+7uVoDkpZXLUQ4x8argo/IwQM6Kh2ma1y83TYMT6XhL1+B 517 5UPcl6RXZBCkiU7nFIG6/0XKFqVWc3fU8e09X+iJwXIJ5Jatywtg+g== 518 -----END RSA PRIVATE KEY----- 519 ` 520 521 func TestCertificateBasedAuth(t *testing.T) { 522 signer, err := ssh.ParsePrivateKey([]byte(SERVER_PEM)) 523 if err != nil { 524 t.Fatalf("unable to parse private key: %v", err) 525 } 526 address := newMockLineServer(t, signer, CLIENT_CERT_SIGNED_BY_SERVER) 527 host, p, _ := net.SplitHostPort(address) 528 port, _ := strconv.Atoi(p) 529 530 connInfo := &connectionInfo{ 531 User: "user", 532 Host: host, 533 PrivateKey: CLIENT_PEM, 534 Certificate: CLIENT_CERT_SIGNED_BY_SERVER, 535 Port: uint16(port), 536 Timeout: "30s", 537 } 538 539 cfg, err := prepareSSHConfig(connInfo) 540 if err != nil { 541 t.Fatal(err) 542 } 543 544 c := &Communicator{ 545 connInfo: connInfo, 546 config: cfg, 547 } 548 549 var cmd remote.Cmd 550 stdout := new(bytes.Buffer) 551 cmd.Command = "echo foo" 552 cmd.Stdout = stdout 553 554 if err := c.Start(&cmd); err != nil { 555 t.Fatal(err) 556 } 557 if err := c.Disconnect(); err != nil { 558 t.Fatal(err) 559 } 560 } 561 562 func TestAccUploadFile(t *testing.T) { 563 // use the local ssh server and scp binary to check uploads 564 if ok := os.Getenv("SSH_UPLOAD_TEST"); ok == "" { 565 t.Log("Skipping Upload Acceptance without SSH_UPLOAD_TEST set") 566 t.Skip() 567 } 568 569 v := cty.ObjectVal(map[string]cty.Value{ 570 "type": cty.StringVal("ssh"), 571 "user": cty.StringVal(os.Getenv("USER")), 572 "host": cty.StringVal("127.0.0.1"), 573 "port": cty.StringVal("22"), 574 "timeout": cty.StringVal("30s"), 575 }) 576 577 c, err := New(v) 578 if err != nil { 579 t.Fatalf("error creating communicator: %s", err) 580 } 581 582 tmpDir := t.TempDir() 583 source, err := os.CreateTemp(tmpDir, "tempfile.in") 584 if err != nil { 585 t.Fatal(err) 586 } 587 588 content := "this is the file content" 589 if _, err := source.WriteString(content); err != nil { 590 t.Fatal(err) 591 } 592 source.Seek(0, io.SeekStart) 593 594 tmpFile := filepath.Join(tmpDir, "tempFile.out") 595 596 testUploadSizeHook = func(size int64) { 597 if size != int64(len(content)) { 598 t.Errorf("expected %d bytes, got %d\n", len(content), size) 599 } 600 } 601 defer func() { 602 testUploadSizeHook = nil 603 }() 604 605 err = c.Upload(tmpFile, source) 606 if err != nil { 607 t.Fatalf("error uploading file: %s", err) 608 } 609 610 data, err := ioutil.ReadFile(tmpFile) 611 if err != nil { 612 t.Fatal(err) 613 } 614 615 if string(data) != content { 616 t.Fatalf("bad: %s", data) 617 } 618 } 619 620 func TestAccHugeUploadFile(t *testing.T) { 621 // use the local ssh server and scp binary to check uploads 622 if ok := os.Getenv("SSH_UPLOAD_TEST"); ok == "" { 623 t.Log("Skipping Upload Acceptance without SSH_UPLOAD_TEST set") 624 t.Skip() 625 } 626 627 v := cty.ObjectVal(map[string]cty.Value{ 628 "type": cty.StringVal("ssh"), 629 "host": cty.StringVal("127.0.0.1"), 630 "user": cty.StringVal(os.Getenv("USER")), 631 "port": cty.StringVal("22"), 632 "timeout": cty.StringVal("30s"), 633 }) 634 635 c, err := New(v) 636 if err != nil { 637 t.Fatalf("error creating communicator: %s", err) 638 } 639 640 // copy 4GB of data, random to prevent compression. 641 size := int64(1 << 32) 642 source := io.LimitReader(rand.New(rand.NewSource(0)), size) 643 644 dest, err := ioutil.TempFile("", "communicator") 645 if err != nil { 646 t.Fatal(err) 647 } 648 destName := dest.Name() 649 dest.Close() 650 defer os.Remove(destName) 651 652 t.Log("Uploading to", destName) 653 654 // bypass the Upload method so we can directly supply the file size 655 // preventing the extra copy of the huge file. 656 targetDir := filepath.Dir(destName) 657 targetFile := filepath.Base(destName) 658 659 scpFunc := func(w io.Writer, stdoutR *bufio.Reader) error { 660 return scpUploadFile(targetFile, source, w, stdoutR, size) 661 } 662 663 cmd, err := quoteShell([]string{"scp", "-vt", targetDir}, c.connInfo.TargetPlatform) 664 if err != nil { 665 t.Fatal(err) 666 } 667 err = c.scpSession(cmd, scpFunc) 668 if err != nil { 669 t.Fatal(err) 670 } 671 672 // check the final file size 673 fs, err := os.Stat(destName) 674 if err != nil { 675 t.Fatal(err) 676 } 677 678 if fs.Size() != size { 679 t.Fatalf("expected file size of %d, got %d", size, fs.Size()) 680 } 681 } 682 683 func TestScriptPath(t *testing.T) { 684 cases := []struct { 685 Input string 686 Pattern string 687 }{ 688 { 689 "/tmp/script.sh", 690 `^/tmp/script\.sh$`, 691 }, 692 { 693 "/tmp/script_%RAND%.sh", 694 `^/tmp/script_(\d+)\.sh$`, 695 }, 696 } 697 698 for _, tc := range cases { 699 v := cty.ObjectVal(map[string]cty.Value{ 700 "type": cty.StringVal("ssh"), 701 "host": cty.StringVal("127.0.0.1"), 702 "script_path": cty.StringVal(tc.Input), 703 }) 704 705 comm, err := New(v) 706 if err != nil { 707 t.Fatalf("err: %s", err) 708 } 709 output := comm.ScriptPath() 710 711 match, err := regexp.Match(tc.Pattern, []byte(output)) 712 if err != nil { 713 t.Fatalf("bad: %s\n\nerr: %s", tc.Input, err) 714 } 715 if !match { 716 t.Fatalf("bad: %s\n\n%s", tc.Input, output) 717 } 718 } 719 } 720 721 func TestScriptPath_randSeed(t *testing.T) { 722 // Pre GH-4186 fix, this value was the deterministic start the pseudorandom 723 // chain of unseeded math/rand values for Int31(). 724 staticSeedPath := "/tmp/terraform_1298498081.sh" 725 c, err := New(cty.ObjectVal(map[string]cty.Value{ 726 "type": cty.StringVal("ssh"), 727 "host": cty.StringVal("127.0.0.1"), 728 })) 729 if err != nil { 730 t.Fatalf("err: %s", err) 731 } 732 path := c.ScriptPath() 733 if path == staticSeedPath { 734 t.Fatalf("rand not seeded! got: %s", path) 735 } 736 } 737 738 var testClientPublicKey = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDE6A1c4n+OtEPEFlNKTZf2i03L3NylSYmvmJ8OLmzLuPZmJBJt4G3VZ/60s1aKzwLKrTq20S+ONG4zvnK5zIPoauoNNdUJKbg944hB4OE+HDbrBhk7SH+YWCsCILBoSXwAVdUEic6FWf/SeqBSmTBySHvpuNOw16J+SK6Ardx8k64F2tRkZuC6AmOZijgKa/sQKjWAIVPk34ECM6OLfPc3kKUEfkdpYLvuMfuRMfSTlxn5lFC0b0SovK9aWfNMBH9iXLQkieQ5rXoyzUC7mwgnASgl8cqw1UrToiUuhvneduXBhbQfmC/Upv+tL6dSSk+0DlgVKEHuJmc8s8+/qpdL` 739 740 func acceptUserPass(goodUser, goodPass string) func(ssh.ConnMetadata, []byte) (*ssh.Permissions, error) { 741 return func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) { 742 if c.User() == goodUser && string(pass) == goodPass { 743 return nil, nil 744 } 745 return nil, fmt.Errorf("password rejected for %q", c.User()) 746 } 747 } 748 749 func acceptPublicKey(keystr string) func(ssh.ConnMetadata, ssh.PublicKey) (*ssh.Permissions, error) { 750 return func(_ ssh.ConnMetadata, inkey ssh.PublicKey) (*ssh.Permissions, error) { 751 goodkey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(keystr)) 752 if err != nil { 753 return nil, fmt.Errorf("error parsing key: %v", err) 754 } 755 756 if bytes.Equal(inkey.Marshal(), goodkey.Marshal()) { 757 return nil, nil 758 } 759 760 return nil, fmt.Errorf("public key rejected") 761 } 762 }