From c0bcc91efeca8301d4f9450ff2da6194550457b0 Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Tue, 20 Feb 2024 20:21:50 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8A=A0=E5=AF=86AES=E5=8C=85=E5=92=8C?= =?UTF-8?q?ssh=E8=BF=9E=E6=8E=A5=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/framework/utils/crypto/aes.go | 88 +++++++ .../utils/crypto/{crypto.go => bcrypt.go} | 0 src/framework/utils/ssh/ssh.go | 229 ++++++++++++++++++ 3 files changed, 317 insertions(+) create mode 100644 src/framework/utils/crypto/aes.go rename src/framework/utils/crypto/{crypto.go => bcrypt.go} (100%) create mode 100644 src/framework/utils/ssh/ssh.go diff --git a/src/framework/utils/crypto/aes.go b/src/framework/utils/crypto/aes.go new file mode 100644 index 00000000..4db36acc --- /dev/null +++ b/src/framework/utils/crypto/aes.go @@ -0,0 +1,88 @@ +package crypto + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "fmt" + "io" +) + +// aesKey 字符串AES加解密密钥 +const aesKey = "AGT66VfY4SMaiT97" + +// StringEncryptByAES 字符串AES加密 +func StringEncryptByAES(text string) (string, error) { + if len(text) == 0 { + return "", nil + } + pass := []byte(text) + xpass, err := aesEncryptWithSalt([]byte(aesKey), pass) + if err == nil { + pass64 := base64.StdEncoding.EncodeToString(xpass) + return pass64, err + } + return "", err +} + +// StringDecryptByAES 字符串AES解密 +func StringDecryptByAES(text string) (string, error) { + if len(text) == 0 { + return "", nil + } + bytesPass, err := base64.StdEncoding.DecodeString(text) + if err != nil { + return "", err + } + var tpass []byte + tpass, err = aesDecryptWithSalt([]byte(aesKey), bytesPass) + if err == nil { + result := string(tpass[:]) + return result, err + } + return "", err +} + +// aesEncryptWithSalt AES加密 +func aesEncryptWithSalt(key, plaintext []byte) ([]byte, error) { + blockSize := aes.BlockSize + padding := blockSize - len(plaintext)%blockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + plaintext = append(plaintext, padtext...) + + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + ciphertext := make([]byte, blockSize+len(plaintext)) + iv := ciphertext[0:blockSize] + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return nil, err + } + cbc := cipher.NewCBCEncrypter(block, iv) + cbc.CryptBlocks(ciphertext[blockSize:], plaintext) + return ciphertext, nil +} + +// aesDecryptWithSalt AES解密 +func aesDecryptWithSalt(key, ciphertext []byte) ([]byte, error) { + blockSize := aes.BlockSize + var block cipher.Block + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + if len(ciphertext) < blockSize { + return nil, fmt.Errorf("iciphertext too short") + } + iv := ciphertext[:blockSize] + ciphertext = ciphertext[blockSize:] + cbc := cipher.NewCBCDecrypter(block, iv) + cbc.CryptBlocks(ciphertext, ciphertext) + length := len(ciphertext) + unpadding := int(ciphertext[len(ciphertext)-1]) + ciphertext = ciphertext[:(length - unpadding)] + return ciphertext, nil +} diff --git a/src/framework/utils/crypto/crypto.go b/src/framework/utils/crypto/bcrypt.go similarity index 100% rename from src/framework/utils/crypto/crypto.go rename to src/framework/utils/crypto/bcrypt.go diff --git a/src/framework/utils/ssh/ssh.go b/src/framework/utils/ssh/ssh.go new file mode 100644 index 00000000..be00b088 --- /dev/null +++ b/src/framework/utils/ssh/ssh.go @@ -0,0 +1,229 @@ +package ssh + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "strings" + "sync" + "time" + + "ems.agt/src/framework/logger" + gossh "golang.org/x/crypto/ssh" +) + +// CopyHost 复制网元主机信息josn同key名 +func CopyHost(to, from interface{}) error { + b, err := json.Marshal(from) + if err != nil { + logger.Errorf("CopyHost Marshal from data err %s", err.Error()) + return err + } + if err = json.Unmarshal(b, to); err != nil { + logger.Errorf("CopyHost Unmarshal to data err %s", err.Error()) + return err + } + return nil +} + +// ConnSSH 连接SSH对象 +type ConnSSH struct { + User string `json:"user"` // 主机用户名 + Addr string `json:"addr"` // 主机地址 + Port int `json:"port"` // SSH端口 + AuthMode string `json:"authMode"` // 认证模式(0密码 1主机私钥) + Password string `json:"password"` // 认证密码 + PrivateKey []byte `json:"privateKey"` // 认证私钥 + PassPhrase []byte `json:"passPhrase"` // 认证私钥密码 + + DialTimeOut time.Duration `json:"dialTimeOut"` // 连接超时断开 + + Client *gossh.Client `json:"client"` + LastResult string `json:"lastResult"` // 记最后一次执行命令的结果 +} + +// NewClient 创建SSH客户端 +func (c *ConnSSH) NewClient() (*ConnSSH, error) { + // IPV6地址协议 + proto := "tcp" + if strings.Contains(c.Addr, ":") { + proto = "tcp6" + c.Addr = fmt.Sprintf("[%s]", c.Addr) + } + addr := fmt.Sprintf("%s:%d", c.Addr, c.Port) + + // ssh客户端配置 + config := &gossh.ClientConfig{} + config.SetDefaults() + config.HostKeyCallback = gossh.InsecureIgnoreHostKey() + config.User = c.User + + // 默认等待5s + if c.DialTimeOut == 0 { + c.DialTimeOut = 5 * time.Second + } + config.Timeout = c.DialTimeOut + + // 认证模式-0密码 1私钥 + if c.AuthMode == "1" { + var signer gossh.Signer + var err error + if len(c.PassPhrase) != 0 { + signer, err = gossh.ParsePrivateKeyWithPassphrase(c.PrivateKey, c.PassPhrase) + } else { + signer, err = gossh.ParsePrivateKey(c.PrivateKey) + } + if err != nil { + return nil, err + } + config.Auth = []gossh.AuthMethod{gossh.PublicKeys(signer)} + } else { + config.Auth = []gossh.AuthMethod{gossh.Password(c.Password)} + } + + client, err := gossh.Dial(proto, addr, config) + if nil != err { + return c, err + } + c.Client = client + return c, nil +} + +// Close 关闭当前SSH客户端 +func (c *ConnSSH) Close() { + if c.Client != nil { + c.Client.Close() + } +} + +// RunCMD 执行单次命令 +func (c *ConnSSH) RunCMD(cmd 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(cmd) + + c.LastResult = string(buf) + return c.LastResult, err +} + +// NewClient 创建SSH客户端会话对象 +func (c *ConnSSH) NewClientSession(cols, rows int) (*SSHClientSession, error) { + sshSession, err := c.Client.NewSession() + if err != nil { + return nil, err + } + + stdin, err := sshSession.StdinPipe() + if err != nil { + return nil, err + } + + comboWriter := new(singleWriter) + 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 &SSHClientSession{ + Stdin: stdin, + Stdout: comboWriter, + Session: sshSession, + }, nil +} + +// SSHClientSession SSH客户端会话对象 +type SSHClientSession struct { + Stdin io.WriteCloser + Stdout *singleWriter + Session *gossh.Session +} + +// Close 关闭会话 +func (s *SSHClientSession) Close() { + if s.Stdin != nil { + s.Stdin.Close() + } + if s.Stdout != nil { + s.Stdout = nil + } + if s.Session != nil { + s.Session.Close() + } +} + +// Write 写入命令 +func (s *SSHClientSession) Write(cmd string) (int, error) { + if s.Stdin == nil { + return 0, fmt.Errorf("stdin is nil to content write failed") + } + if strings.LastIndexByte(cmd, '\n') == -1 { + cmd = fmt.Sprintln(cmd) + } + return s.Stdin.Write([]byte(cmd)) +} + +// Read 读取结果 +func (s *SSHClientSession) Read() []byte { + if s.Stdout == nil { + return []byte{} + } + time.Sleep(300 * time.Millisecond) + bs := s.Stdout.Bytes() + if len(bs) > 0 { + s.Stdout.Reset() + return bs + } + return []byte{} +} + +// CombinedOutput 发送命令带结果返回 +func (s *SSHClientSession) CombinedOutput(cmd string) (string, error) { + n, err := s.Write(cmd) + if n == 0 || err != nil { + return "", err + } + return string(s.Read()), nil +} + +// singleWriter SSH客户端会话消息 +type singleWriter struct { + b bytes.Buffer + mu sync.Mutex +} + +func (w *singleWriter) Write(p []byte) (int, error) { + w.mu.Lock() + defer w.mu.Unlock() + return w.b.Write(p) +} +func (w *singleWriter) Bytes() []byte { + w.mu.Lock() + defer w.mu.Unlock() + return w.b.Bytes() +} +func (w *singleWriter) Reset() { + w.mu.Lock() + defer w.mu.Unlock() + w.b.Reset() +}