func newSSHListener(sshClient *ssh.Client, exposedBind string, exposedPort int) (net.Listener, error) { sshListener, err := sshClient.Listen("tcp", joinHostPort(exposedBind, exposedPort)) if err != nil { return nil, err } return sshListener, nil }
func (s *sshTunnel) Start(readyErrCh chan<- error, errCh chan<- error) { authMethods := []ssh.AuthMethod{} if s.options.PrivateKey != "" { s.logger.Debug(s.logTag, "Reading private key file '%s'", s.options.PrivateKey) keyContents, err := ioutil.ReadFile(s.options.PrivateKey) if err != nil { readyErrCh <- bosherr.WrapErrorf(err, "Reading private key file '%s'", s.options.PrivateKey) return } s.logger.Debug(s.logTag, "Parsing private key file '%s'", s.options.PrivateKey) signer, err := ssh.ParsePrivateKey(keyContents) if err != nil { readyErrCh <- bosherr.WrapErrorf(err, "Parsing private key file '%s'", s.options.PrivateKey) return } authMethods = append(authMethods, ssh.PublicKeys(signer)) } if s.options.Password != "" { s.logger.Debug(s.logTag, "Adding password auth method to ssh tunnel config") keyboardInteractiveChallenge := func( user, instruction string, questions []string, echos []bool, ) (answers []string, err error) { if len(questions) == 0 { return []string{}, nil } return []string{s.options.Password}, nil } authMethods = append(authMethods, ssh.KeyboardInteractive(keyboardInteractiveChallenge)) authMethods = append(authMethods, ssh.Password(s.options.Password)) } sshConfig := &ssh.ClientConfig{ User: s.options.User, Auth: authMethods, } s.logger.Debug(s.logTag, "Dialing remote server at %s:%d", s.options.Host, s.options.Port) remoteAddr := fmt.Sprintf("%s:%d", s.options.Host, s.options.Port) retryStrategy := &SSHRetryStrategy{ TimeService: s.timeService, ConnectionRefusedTimeout: s.connectionRefusedTimeout, AuthFailureTimeout: s.authFailureTimeout, } var conn *ssh.Client var err error for i := 0; ; i++ { s.logger.Debug(s.logTag, "Making attempt #%d", i) conn, err = ssh.Dial("tcp", remoteAddr, sshConfig) if err == nil { break } if !retryStrategy.IsRetryable(err) { readyErrCh <- bosherr.WrapError(err, "Failed to connect to remote server") return } s.logger.Debug(s.logTag, "Attempt failed #%d: Dialing remote server: %s", i, err.Error()) time.Sleep(s.startDialDelay) } remoteListenAddr := fmt.Sprintf("127.0.0.1:%d", s.options.RemoteForwardPort) s.logger.Debug(s.logTag, "Listening on remote server %s", remoteListenAddr) s.remoteListener, err = conn.Listen("tcp", remoteListenAddr) if err != nil { readyErrCh <- bosherr.WrapError(err, "Listening on remote server") return } readyErrCh <- nil for { remoteConn, err := s.remoteListener.Accept() s.logger.Debug(s.logTag, "Received connection") if err != nil { errCh <- bosherr.WrapError(err, "Accepting connection on remote server") } defer func() { if err = remoteConn.Close(); err != nil { s.logger.Warn(s.logTag, "Failed to close remote listener connection: %s", err.Error()) } }() s.logger.Debug(s.logTag, "Dialing local server") localDialAddr := fmt.Sprintf("127.0.0.1:%d", s.options.LocalForwardPort) localConn, err := net.Dial("tcp", localDialAddr) if err != nil { errCh <- bosherr.WrapError(err, "Dialing local server") return } go func() { bytesNum, err := io.Copy(remoteConn, localConn) defer func() { if err = localConn.Close(); err != nil { s.logger.Warn(s.logTag, "Failed to close local dial connection: %s", err.Error()) } }() s.logger.Debug(s.logTag, "Copying bytes from local to remote %d", bytesNum) if err != nil { errCh <- bosherr.WrapError(err, "Copying bytes from local to remote") } }() go func() { bytesNum, err := io.Copy(localConn, remoteConn) defer func() { if err = localConn.Close(); err != nil { s.logger.Warn(s.logTag, "Failed to close local dial connection: %s", err.Error()) } }() s.logger.Debug(s.logTag, "Copying bytes from remote to local %d", bytesNum) if err != nil { errCh <- bosherr.WrapError(err, "Copying bytes from remote to local") } }() } }
func (beacon *Beacon) run(command string, client *ssh.Client, signals <-chan os.Signal, ready chan<- struct{}) error { keepaliveFailed, cancelKeepalive := beacon.keepAlive(client) sess, err := client.NewSession() if err != nil { return fmt.Errorf("failed to create session: %s", err) } defer sess.Close() workerPayload, err := json.Marshal(beacon.Worker) if err != nil { return err } sess.Stdin = bytes.NewBuffer(workerPayload) sess.Stdout = os.Stdout sess.Stderr = os.Stderr err = sess.Start(command) if err != nil { return err } gardenRemoteListener, err := client.Listen("tcp", gardenForwardAddr) if err != nil { return fmt.Errorf("failed to listen remotely: %s", err) } go beacon.proxyListenerTo(gardenRemoteListener, beacon.Worker.GardenAddr) bcURL, err := url.Parse(beacon.Worker.BaggageclaimURL) if err != nil { return fmt.Errorf("failed to parse baggageclaim url: %s", err) } baggageclaimRemoteListener, err := client.Listen("tcp", baggageclaimForwardAddr) if err != nil { return fmt.Errorf("failed to listen remotely: %s", err) } go beacon.proxyListenerTo(baggageclaimRemoteListener, bcURL.Host) close(ready) exited := make(chan error, 1) go func() { exited <- sess.Wait() }() select { case <-signals: close(cancelKeepalive) sess.Close() <-exited // don't bother waiting for keepalive return nil case err := <-exited: return err case err := <-keepaliveFailed: return err } return nil }