mirror of https://github.com/1Panel-dev/1Panel
151 lines
3.4 KiB
Go
151 lines
3.4 KiB
Go
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
|
|
}
|