github.com/hs0210/hashicorp-terraform@v0.11.12-beta1/communicator/ssh/communicator.go (about) 1 package ssh 2 3 import ( 4 "bufio" 5 "bytes" 6 "errors" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "log" 11 "math/rand" 12 "net" 13 "os" 14 "path/filepath" 15 "strconv" 16 "strings" 17 "sync" 18 "time" 19 20 "github.com/hashicorp/terraform/communicator/remote" 21 "github.com/hashicorp/terraform/terraform" 22 "golang.org/x/crypto/ssh" 23 "golang.org/x/crypto/ssh/agent" 24 ) 25 26 const ( 27 // DefaultShebang is added at the top of a SSH script file 28 DefaultShebang = "#!/bin/sh\n" 29 ) 30 31 // randShared is a global random generator object that is shared. 32 // This must be shared since it is seeded by the current time and 33 // creating multiple can result in the same values. By using a shared 34 // RNG we assure different numbers per call. 35 var randLock sync.Mutex 36 var randShared *rand.Rand 37 38 // Communicator represents the SSH communicator 39 type Communicator struct { 40 connInfo *connectionInfo 41 client *ssh.Client 42 config *sshConfig 43 conn net.Conn 44 address string 45 46 lock sync.Mutex 47 } 48 49 type sshConfig struct { 50 // The configuration of the Go SSH connection 51 config *ssh.ClientConfig 52 53 // connection returns a new connection. The current connection 54 // in use will be closed as part of the Close method, or in the 55 // case an error occurs. 56 connection func() (net.Conn, error) 57 58 // noPty, if true, will not request a pty from the remote end. 59 noPty bool 60 61 // sshAgent is a struct surrounding the agent.Agent client and the net.Conn 62 // to the SSH Agent. It is nil if no SSH agent is configured 63 sshAgent *sshAgent 64 } 65 66 type fatalError struct { 67 error 68 } 69 70 func (e fatalError) FatalError() error { 71 return e.error 72 } 73 74 // New creates a new communicator implementation over SSH. 75 func New(s *terraform.InstanceState) (*Communicator, error) { 76 connInfo, err := parseConnectionInfo(s) 77 if err != nil { 78 return nil, err 79 } 80 81 config, err := prepareSSHConfig(connInfo) 82 if err != nil { 83 return nil, err 84 } 85 86 // Setup the random number generator once. The seed value is the 87 // time multiplied by the PID. This can overflow the int64 but that 88 // is okay. We multiply by the PID in case we have multiple processes 89 // grabbing this at the same time. This is possible with Terraform and 90 // if we communicate to the same host at the same instance, we could 91 // overwrite the same files. Multiplying by the PID prevents this. 92 randLock.Lock() 93 defer randLock.Unlock() 94 if randShared == nil { 95 randShared = rand.New(rand.NewSource( 96 time.Now().UnixNano() * int64(os.Getpid()))) 97 } 98 99 comm := &Communicator{ 100 connInfo: connInfo, 101 config: config, 102 } 103 104 return comm, nil 105 } 106 107 // Connect implementation of communicator.Communicator interface 108 func (c *Communicator) Connect(o terraform.UIOutput) (err error) { 109 // Grab a lock so we can modify our internal attributes 110 c.lock.Lock() 111 defer c.lock.Unlock() 112 113 if c.conn != nil { 114 c.conn.Close() 115 } 116 117 // Set the conn and client to nil since we'll recreate it 118 c.conn = nil 119 c.client = nil 120 121 if o != nil { 122 o.Output(fmt.Sprintf( 123 "Connecting to remote host via SSH...\n"+ 124 " Host: %s\n"+ 125 " User: %s\n"+ 126 " Password: %t\n"+ 127 " Private key: %t\n"+ 128 " SSH Agent: %t\n"+ 129 " Checking Host Key: %t", 130 c.connInfo.Host, c.connInfo.User, 131 c.connInfo.Password != "", 132 c.connInfo.PrivateKey != "", 133 c.connInfo.Agent, 134 c.connInfo.HostKey != "", 135 )) 136 137 if c.connInfo.BastionHost != "" { 138 o.Output(fmt.Sprintf( 139 "Using configured bastion host...\n"+ 140 " Host: %s\n"+ 141 " User: %s\n"+ 142 " Password: %t\n"+ 143 " Private key: %t\n"+ 144 " SSH Agent: %t\n"+ 145 " Checking Host Key: %t", 146 c.connInfo.BastionHost, c.connInfo.BastionUser, 147 c.connInfo.BastionPassword != "", 148 c.connInfo.BastionPrivateKey != "", 149 c.connInfo.Agent, 150 c.connInfo.BastionHostKey != "", 151 )) 152 } 153 } 154 155 log.Printf("[DEBUG] connecting to TCP connection for SSH") 156 c.conn, err = c.config.connection() 157 if err != nil { 158 // Explicitly set this to the REAL nil. Connection() can return 159 // a nil implementation of net.Conn which will make the 160 // "if c.conn == nil" check fail above. Read here for more information 161 // on this psychotic language feature: 162 // 163 // http://golang.org/doc/faq#nil_error 164 c.conn = nil 165 166 log.Printf("[ERROR] connection error: %s", err) 167 return err 168 } 169 170 log.Printf("[DEBUG] handshaking with SSH") 171 host := fmt.Sprintf("%s:%d", c.connInfo.Host, c.connInfo.Port) 172 sshConn, sshChan, req, err := ssh.NewClientConn(c.conn, host, c.config.config) 173 if err != nil { 174 // While in theory this should be a fatal error, some hosts may start 175 // the ssh service before it is properly configured, or before user 176 // authentication data is available. 177 // Log the error, and allow the provisioner to retry. 178 log.Printf("[WARN] %s", err) 179 return err 180 } 181 182 c.client = ssh.NewClient(sshConn, sshChan, req) 183 184 if c.config.sshAgent != nil { 185 log.Printf("[DEBUG] Telling SSH config to forward to agent") 186 if err := c.config.sshAgent.ForwardToAgent(c.client); err != nil { 187 return fatalError{err} 188 } 189 190 log.Printf("[DEBUG] Setting up a session to request agent forwarding") 191 session, err := c.newSession() 192 if err != nil { 193 return err 194 } 195 defer session.Close() 196 197 err = agent.RequestAgentForwarding(session) 198 199 if err == nil { 200 log.Printf("[INFO] agent forwarding enabled") 201 } else { 202 log.Printf("[WARN] error forwarding agent: %s", err) 203 } 204 } 205 206 if o != nil { 207 o.Output("Connected!") 208 } 209 210 return err 211 } 212 213 // Disconnect implementation of communicator.Communicator interface 214 func (c *Communicator) Disconnect() error { 215 c.lock.Lock() 216 defer c.lock.Unlock() 217 218 if c.config.sshAgent != nil { 219 if err := c.config.sshAgent.Close(); err != nil { 220 return err 221 } 222 } 223 224 if c.conn != nil { 225 conn := c.conn 226 c.conn = nil 227 return conn.Close() 228 } 229 230 return nil 231 } 232 233 // Timeout implementation of communicator.Communicator interface 234 func (c *Communicator) Timeout() time.Duration { 235 return c.connInfo.TimeoutVal 236 } 237 238 // ScriptPath implementation of communicator.Communicator interface 239 func (c *Communicator) ScriptPath() string { 240 randLock.Lock() 241 defer randLock.Unlock() 242 243 return strings.Replace( 244 c.connInfo.ScriptPath, "%RAND%", 245 strconv.FormatInt(int64(randShared.Int31()), 10), -1) 246 } 247 248 // Start implementation of communicator.Communicator interface 249 func (c *Communicator) Start(cmd *remote.Cmd) error { 250 cmd.Init() 251 252 session, err := c.newSession() 253 if err != nil { 254 return err 255 } 256 257 // Setup our session 258 session.Stdin = cmd.Stdin 259 session.Stdout = cmd.Stdout 260 session.Stderr = cmd.Stderr 261 262 if !c.config.noPty { 263 // Request a PTY 264 termModes := ssh.TerminalModes{ 265 ssh.ECHO: 0, // do not echo 266 ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud 267 ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud 268 } 269 270 if err := session.RequestPty("xterm", 80, 40, termModes); err != nil { 271 return err 272 } 273 } 274 275 log.Printf("[DEBUG] starting remote command: %s", cmd.Command) 276 err = session.Start(strings.TrimSpace(cmd.Command) + "\n") 277 if err != nil { 278 return err 279 } 280 281 // Start a goroutine to wait for the session to end and set the 282 // exit boolean and status. 283 go func() { 284 defer session.Close() 285 286 err := session.Wait() 287 exitStatus := 0 288 if err != nil { 289 exitErr, ok := err.(*ssh.ExitError) 290 if ok { 291 exitStatus = exitErr.ExitStatus() 292 } 293 } 294 295 cmd.SetExitStatus(exitStatus, err) 296 log.Printf("[DEBUG] remote command exited with '%d': %s", exitStatus, cmd.Command) 297 }() 298 299 return nil 300 } 301 302 // Upload implementation of communicator.Communicator interface 303 func (c *Communicator) Upload(path string, input io.Reader) error { 304 // The target directory and file for talking the SCP protocol 305 targetDir := filepath.Dir(path) 306 targetFile := filepath.Base(path) 307 308 // On windows, filepath.Dir uses backslash separators (ie. "\tmp"). 309 // This does not work when the target host is unix. Switch to forward slash 310 // which works for unix and windows 311 targetDir = filepath.ToSlash(targetDir) 312 313 // Skip copying if we can get the file size directly from common io.Readers 314 size := int64(0) 315 316 switch src := input.(type) { 317 case *os.File: 318 fi, err := src.Stat() 319 if err != nil { 320 size = fi.Size() 321 } 322 case *bytes.Buffer: 323 size = int64(src.Len()) 324 case *bytes.Reader: 325 size = int64(src.Len()) 326 case *strings.Reader: 327 size = int64(src.Len()) 328 } 329 330 scpFunc := func(w io.Writer, stdoutR *bufio.Reader) error { 331 return scpUploadFile(targetFile, input, w, stdoutR, size) 332 } 333 334 return c.scpSession("scp -vt "+targetDir, scpFunc) 335 } 336 337 // UploadScript implementation of communicator.Communicator interface 338 func (c *Communicator) UploadScript(path string, input io.Reader) error { 339 reader := bufio.NewReader(input) 340 prefix, err := reader.Peek(2) 341 if err != nil { 342 return fmt.Errorf("Error reading script: %s", err) 343 } 344 345 var script bytes.Buffer 346 if string(prefix) != "#!" { 347 script.WriteString(DefaultShebang) 348 } 349 350 script.ReadFrom(reader) 351 if err := c.Upload(path, &script); err != nil { 352 return err 353 } 354 355 var stdout, stderr bytes.Buffer 356 cmd := &remote.Cmd{ 357 Command: fmt.Sprintf("chmod 0777 %s", path), 358 Stdout: &stdout, 359 Stderr: &stderr, 360 } 361 if err := c.Start(cmd); err != nil { 362 return fmt.Errorf( 363 "Error chmodding script file to 0777 in remote "+ 364 "machine: %s", err) 365 } 366 367 if err := cmd.Wait(); err != nil { 368 return fmt.Errorf( 369 "Error chmodding script file to 0777 in remote "+ 370 "machine %v: %s %s", err, stdout.String(), stderr.String()) 371 } 372 373 return nil 374 } 375 376 // UploadDir implementation of communicator.Communicator interface 377 func (c *Communicator) UploadDir(dst string, src string) error { 378 log.Printf("[DEBUG] Uploading dir '%s' to '%s'", src, dst) 379 scpFunc := func(w io.Writer, r *bufio.Reader) error { 380 uploadEntries := func() error { 381 f, err := os.Open(src) 382 if err != nil { 383 return err 384 } 385 defer f.Close() 386 387 entries, err := f.Readdir(-1) 388 if err != nil { 389 return err 390 } 391 392 return scpUploadDir(src, entries, w, r) 393 } 394 395 if src[len(src)-1] != '/' { 396 log.Printf("[DEBUG] No trailing slash, creating the source directory name") 397 return scpUploadDirProtocol(filepath.Base(src), w, r, uploadEntries) 398 } 399 // Trailing slash, so only upload the contents 400 return uploadEntries() 401 } 402 403 return c.scpSession("scp -rvt "+dst, scpFunc) 404 } 405 406 func (c *Communicator) newSession() (session *ssh.Session, err error) { 407 log.Println("[DEBUG] opening new ssh session") 408 if c.client == nil { 409 err = errors.New("ssh client is not connected") 410 } else { 411 session, err = c.client.NewSession() 412 } 413 414 if err != nil { 415 log.Printf("[WARN] ssh session open error: '%s', attempting reconnect", err) 416 if err := c.Connect(nil); err != nil { 417 return nil, err 418 } 419 420 return c.client.NewSession() 421 } 422 423 return session, nil 424 } 425 426 func (c *Communicator) scpSession(scpCommand string, f func(io.Writer, *bufio.Reader) error) error { 427 session, err := c.newSession() 428 if err != nil { 429 return err 430 } 431 defer session.Close() 432 433 // Get a pipe to stdin so that we can send data down 434 stdinW, err := session.StdinPipe() 435 if err != nil { 436 return err 437 } 438 439 // We only want to close once, so we nil w after we close it, 440 // and only close in the defer if it hasn't been closed already. 441 defer func() { 442 if stdinW != nil { 443 stdinW.Close() 444 } 445 }() 446 447 // Get a pipe to stdout so that we can get responses back 448 stdoutPipe, err := session.StdoutPipe() 449 if err != nil { 450 return err 451 } 452 stdoutR := bufio.NewReader(stdoutPipe) 453 454 // Set stderr to a bytes buffer 455 stderr := new(bytes.Buffer) 456 session.Stderr = stderr 457 458 // Start the sink mode on the other side 459 // TODO(mitchellh): There are probably issues with shell escaping the path 460 log.Println("[DEBUG] Starting remote scp process: ", scpCommand) 461 if err := session.Start(scpCommand); err != nil { 462 return err 463 } 464 465 // Call our callback that executes in the context of SCP. We ignore 466 // EOF errors if they occur because it usually means that SCP prematurely 467 // ended on the other side. 468 log.Println("[DEBUG] Started SCP session, beginning transfers...") 469 if err := f(stdinW, stdoutR); err != nil && err != io.EOF { 470 return err 471 } 472 473 // Close the stdin, which sends an EOF, and then set w to nil so that 474 // our defer func doesn't close it again since that is unsafe with 475 // the Go SSH package. 476 log.Println("[DEBUG] SCP session complete, closing stdin pipe.") 477 stdinW.Close() 478 stdinW = nil 479 480 // Wait for the SCP connection to close, meaning it has consumed all 481 // our data and has completed. Or has errored. 482 log.Println("[DEBUG] Waiting for SSH session to complete.") 483 err = session.Wait() 484 if err != nil { 485 if exitErr, ok := err.(*ssh.ExitError); ok { 486 // Otherwise, we have an ExitErorr, meaning we can just read 487 // the exit status 488 log.Printf("[ERROR] %s", exitErr) 489 490 // If we exited with status 127, it means SCP isn't available. 491 // Return a more descriptive error for that. 492 if exitErr.ExitStatus() == 127 { 493 return errors.New( 494 "SCP failed to start. This usually means that SCP is not\n" + 495 "properly installed on the remote system.") 496 } 497 } 498 499 return err 500 } 501 502 scpErr := stderr.String() 503 if len(scpErr) > 0 { 504 log.Printf("[ERROR] scp stderr: %q", stderr) 505 } 506 507 return nil 508 } 509 510 // checkSCPStatus checks that a prior command sent to SCP completed 511 // successfully. If it did not complete successfully, an error will 512 // be returned. 513 func checkSCPStatus(r *bufio.Reader) error { 514 code, err := r.ReadByte() 515 if err != nil { 516 return err 517 } 518 519 if code != 0 { 520 // Treat any non-zero (really 1 and 2) as fatal errors 521 message, _, err := r.ReadLine() 522 if err != nil { 523 return fmt.Errorf("Error reading error message: %s", err) 524 } 525 526 return errors.New(string(message)) 527 } 528 529 return nil 530 } 531 532 func scpUploadFile(dst string, src io.Reader, w io.Writer, r *bufio.Reader, size int64) error { 533 if size == 0 { 534 // Create a temporary file where we can copy the contents of the src 535 // so that we can determine the length, since SCP is length-prefixed. 536 tf, err := ioutil.TempFile("", "terraform-upload") 537 if err != nil { 538 return fmt.Errorf("Error creating temporary file for upload: %s", err) 539 } 540 defer os.Remove(tf.Name()) 541 defer tf.Close() 542 543 log.Println("[DEBUG] Copying input data into temporary file so we can read the length") 544 if _, err := io.Copy(tf, src); err != nil { 545 return err 546 } 547 548 // Sync the file so that the contents are definitely on disk, then 549 // read the length of it. 550 if err := tf.Sync(); err != nil { 551 return fmt.Errorf("Error creating temporary file for upload: %s", err) 552 } 553 554 // Seek the file to the beginning so we can re-read all of it 555 if _, err := tf.Seek(0, 0); err != nil { 556 return fmt.Errorf("Error creating temporary file for upload: %s", err) 557 } 558 559 fi, err := tf.Stat() 560 if err != nil { 561 return fmt.Errorf("Error creating temporary file for upload: %s", err) 562 } 563 564 src = tf 565 size = fi.Size() 566 } 567 568 // Start the protocol 569 log.Println("[DEBUG] Beginning file upload...") 570 fmt.Fprintln(w, "C0644", size, dst) 571 if err := checkSCPStatus(r); err != nil { 572 return err 573 } 574 575 if _, err := io.Copy(w, src); err != nil { 576 return err 577 } 578 579 fmt.Fprint(w, "\x00") 580 if err := checkSCPStatus(r); err != nil { 581 return err 582 } 583 584 return nil 585 } 586 587 func scpUploadDirProtocol(name string, w io.Writer, r *bufio.Reader, f func() error) error { 588 log.Printf("[DEBUG] SCP: starting directory upload: %s", name) 589 fmt.Fprintln(w, "D0755 0", name) 590 err := checkSCPStatus(r) 591 if err != nil { 592 return err 593 } 594 595 if err := f(); err != nil { 596 return err 597 } 598 599 fmt.Fprintln(w, "E") 600 if err != nil { 601 return err 602 } 603 604 return nil 605 } 606 607 func scpUploadDir(root string, fs []os.FileInfo, w io.Writer, r *bufio.Reader) error { 608 for _, fi := range fs { 609 realPath := filepath.Join(root, fi.Name()) 610 611 // Track if this is actually a symlink to a directory. If it is 612 // a symlink to a file we don't do any special behavior because uploading 613 // a file just works. If it is a directory, we need to know so we 614 // treat it as such. 615 isSymlinkToDir := false 616 if fi.Mode()&os.ModeSymlink == os.ModeSymlink { 617 symPath, err := filepath.EvalSymlinks(realPath) 618 if err != nil { 619 return err 620 } 621 622 symFi, err := os.Lstat(symPath) 623 if err != nil { 624 return err 625 } 626 627 isSymlinkToDir = symFi.IsDir() 628 } 629 630 if !fi.IsDir() && !isSymlinkToDir { 631 // It is a regular file (or symlink to a file), just upload it 632 f, err := os.Open(realPath) 633 if err != nil { 634 return err 635 } 636 637 err = func() error { 638 defer f.Close() 639 return scpUploadFile(fi.Name(), f, w, r, fi.Size()) 640 }() 641 642 if err != nil { 643 return err 644 } 645 646 continue 647 } 648 649 // It is a directory, recursively upload 650 err := scpUploadDirProtocol(fi.Name(), w, r, func() error { 651 f, err := os.Open(realPath) 652 if err != nil { 653 return err 654 } 655 defer f.Close() 656 657 entries, err := f.Readdir(-1) 658 if err != nil { 659 return err 660 } 661 662 return scpUploadDir(realPath, entries, w, r) 663 }) 664 if err != nil { 665 return err 666 } 667 } 668 669 return nil 670 } 671 672 // ConnectFunc is a convenience method for returning a function 673 // that just uses net.Dial to communicate with the remote end that 674 // is suitable for use with the SSH communicator configuration. 675 func ConnectFunc(network, addr string) func() (net.Conn, error) { 676 return func() (net.Conn, error) { 677 c, err := net.DialTimeout(network, addr, 15*time.Second) 678 if err != nil { 679 return nil, err 680 } 681 682 if tcpConn, ok := c.(*net.TCPConn); ok { 683 tcpConn.SetKeepAlive(true) 684 } 685 686 return c, nil 687 } 688 } 689 690 // BastionConnectFunc is a convenience method for returning a function 691 // that connects to a host over a bastion connection. 692 func BastionConnectFunc( 693 bProto string, 694 bAddr string, 695 bConf *ssh.ClientConfig, 696 proto string, 697 addr string) func() (net.Conn, error) { 698 return func() (net.Conn, error) { 699 log.Printf("[DEBUG] Connecting to bastion: %s", bAddr) 700 bastion, err := ssh.Dial(bProto, bAddr, bConf) 701 if err != nil { 702 return nil, fmt.Errorf("Error connecting to bastion: %s", err) 703 } 704 705 log.Printf("[DEBUG] Connecting via bastion (%s) to host: %s", bAddr, addr) 706 conn, err := bastion.Dial(proto, addr) 707 if err != nil { 708 bastion.Close() 709 return nil, err 710 } 711 712 // Wrap it up so we close both things properly 713 return &bastionConn{ 714 Conn: conn, 715 Bastion: bastion, 716 }, nil 717 } 718 } 719 720 type bastionConn struct { 721 net.Conn 722 Bastion *ssh.Client 723 } 724 725 func (c *bastionConn) Close() error { 726 c.Conn.Close() 727 return c.Bastion.Close() 728 }