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