package ssh import ( "bytes" "fmt" "io" "net" "sync" "time" "github.com/1Panel-dev/1Panel/backend/global" gossh "golang.org/x/crypto/ssh" ) type ConnInfo struct { User string `json:"user"` Addr string `json:"addr"` Port int `json:"port"` AuthMode string `json:"authMode"` Password string `json:"password"` PrivateKey []byte `json:"privateKey"` PassPhrase []byte `json:"passPhrase"` DialTimeOut time.Duration `json:"dialTimeOut"` Client *gossh.Client `json:"client"` Session *gossh.Session `json:"session"` LastResult string `json:"lastResult"` } func (c *ConnInfo) NewClient() (*ConnInfo, error) { config := &gossh.ClientConfig{} config.SetDefaults() addr := fmt.Sprintf("%s:%d", c.Addr, c.Port) config.User = c.User if c.AuthMode == "password" { config.Auth = []gossh.AuthMethod{gossh.Password(c.Password)} } else { signer, err := makePrivateKeySigner(c.PrivateKey, c.PassPhrase) if err != nil { return nil, err } config.Auth = []gossh.AuthMethod{gossh.PublicKeys(signer)} } if c.DialTimeOut == 0 { c.DialTimeOut = 5 * time.Second } config.Timeout = c.DialTimeOut config.HostKeyCallback = func(hostname string, remote net.Addr, key gossh.PublicKey) error { return nil } client, err := gossh.Dial("tcp", addr, config) if nil != err { return c, err } c.Client = client return c, nil } func (c *ConnInfo) Run(shell string) (string, error) { if c.Client == nil { if _, err := c.NewClient(); err != nil { return "", err } } session, err := c.Client.NewSession() if err != nil { return "", err } defer session.Close() buf, err := session.CombinedOutput(shell) c.LastResult = string(buf) return c.LastResult, err } func (c *ConnInfo) Close() { if err := c.Client.Close(); err != nil { global.LOG.Errorf("close ssh client failed, err: %v", err) } } type SshConn struct { StdinPipe io.WriteCloser ComboOutput *wsBufferWriter Session *gossh.Session } func (c *ConnInfo) NewSshConn(cols, rows int) (*SshConn, error) { sshSession, err := c.Client.NewSession() if err != nil { return nil, err } stdinP, err := sshSession.StdinPipe() if err != nil { return nil, err } comboWriter := new(wsBufferWriter) sshSession.Stdout = comboWriter sshSession.Stderr = comboWriter modes := gossh.TerminalModes{ gossh.ECHO: 1, gossh.TTY_OP_ISPEED: 14400, gossh.TTY_OP_OSPEED: 14400, } if err := sshSession.RequestPty("xterm", rows, cols, modes); err != nil { return nil, err } if err := sshSession.Shell(); err != nil { return nil, err } return &SshConn{StdinPipe: stdinP, ComboOutput: comboWriter, Session: sshSession}, nil } func (s *SshConn) Close() { if s.Session != nil { s.Session.Close() } } type wsBufferWriter struct { buffer bytes.Buffer mu sync.Mutex } func (w *wsBufferWriter) Write(p []byte) (int, error) { w.mu.Lock() defer w.mu.Unlock() return w.buffer.Write(p) } func makePrivateKeySigner(privateKey []byte, passPhrase []byte) (gossh.Signer, error) { var signer gossh.Signer if passPhrase != nil { s, err := gossh.ParsePrivateKeyWithPassphrase(privateKey, passPhrase) if err != nil { return nil, fmt.Errorf("error parsing SSH key: '%v'", err) } signer = s } else { s, err := gossh.ParsePrivateKey(privateKey) if err != nil { return nil, fmt.Errorf("error parsing SSH key: '%v'", err) } signer = s } return signer, nil }