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 ExampleNewServerConn() { // An SSH server is represented by a ServerConfig, which holds // certificate details and handles authentication of ServerConns. config := &ssh.ServerConfig{ PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) { // Should use constant-time compare (or better, salt+hash) in // a production setting. if c.User() == "testuser" && string(pass) == "tiger" { return nil, nil } return nil, fmt.Errorf("password rejected for %q", c.User()) }, } privateBytes, err := ioutil.ReadFile("id_rsa") if err != nil { panic("Failed to load private key") } private, err := ssh.ParsePrivateKey(privateBytes) if err != nil { panic("Failed to parse private key") } config.AddHostKey(private) // Once a ServerConfig has been configured, connections can be // accepted. listener, err := net.Listen("tcp", "0.0.0.0:2022") if err != nil { panic("failed to listen for connection") } nConn, err := listener.Accept() if err != nil { panic("failed to accept incoming connection") } // Before use, a handshake must be performed on the incoming // net.Conn. _, chans, reqs, err := ssh.NewServerConn(nConn, config) if err != nil { panic("failed to handshake") } // The incoming Request channel must be serviced. go ssh.DiscardRequests(reqs) // Service the incoming Channel channel. for newChannel := range chans { // Channels have a type, depending on the application level // protocol intended. In the case of a shell, the type is // "session" and ServerShell may be used to present a simple // terminal interface. if newChannel.ChannelType() != "session" { newChannel.Reject(ssh.UnknownChannelType, "unknown channel type") continue } channel, requests, err := newChannel.Accept() if err != nil { panic("could not accept channel.") } // Sessions have out-of-band requests such as "shell", // "pty-req" and "env". Here we handle only the // "shell" request. go func(in <-chan *ssh.Request) { for req := range in { ok := false switch req.Type { case "shell": ok = true if len(req.Payload) > 0 { // We don't accept any // commands, only the // default shell. ok = false } } req.Reply(ok, nil) } }(requests) term := terminal.NewTerminal(channel, "> ") go func() { defer channel.Close() for { line, err := term.ReadLine() if err != nil { break } fmt.Println(line) } }() } }