Files
mayfly-go/server/internal/machine/mcm/machine.go

181 lines
4.9 KiB
Go
Raw Normal View History

2023-10-30 17:34:56 +08:00
package mcm
import (
"fmt"
tagentity "mayfly-go/internal/tag/domain/entity"
2023-10-30 17:34:56 +08:00
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/model"
2024-05-21 04:06:13 +00:00
"mayfly-go/pkg/utils/netx"
2023-10-30 17:34:56 +08:00
"net"
"strings"
2023-10-30 17:34:56 +08:00
"time"
"golang.org/x/crypto/ssh"
)
// 机器信息
type MachineInfo struct {
model.ExtraData
Key string `json:"key"` // 缓存key
Id uint64 `json:"id"`
Name string `json:"name"`
2024-05-21 04:06:13 +00:00
Code string `json:"code"`
Protocol int `json:"protocol"`
2023-10-30 17:34:56 +08:00
Ip string `json:"ip"` // IP地址
Port int `json:"-"` // 端口号
AuthCertName string `json:"authCertName"`
AuthCertType tagentity.AuthCertType `json:"-"`
AuthMethod int8 `json:"-"` // 授权认证方式
Username string `json:"-"` // 用户名
Password string `json:"-"`
Passphrase string `json:"-"` // 私钥口令
2023-10-30 17:34:56 +08:00
SshTunnelMachine *MachineInfo `json:"-"` // ssh隧道机器
TempSshMachineId uint64 `json:"-"` // ssh隧道机器id用于记录隧道机器id连接出错后关闭隧道
2023-10-30 17:34:56 +08:00
EnableRecorder int8 `json:"-"` // 是否启用终端回放记录
CodePath []string `json:"codePath"`
2023-10-30 17:34:56 +08:00
}
2024-05-21 04:06:13 +00:00
func (mi *MachineInfo) UseSshTunnel() bool {
return mi.SshTunnelMachine != nil
2023-10-30 17:34:56 +08:00
}
2024-05-21 04:06:13 +00:00
func (mi *MachineInfo) GetTunnelId() string {
return fmt.Sprintf("machine:%d", mi.Id)
2023-12-20 23:01:51 +08:00
}
2023-10-30 17:34:56 +08:00
// 连接
func (mi *MachineInfo) Conn() (*Cli, error) {
2024-11-20 22:43:53 +08:00
logx.Infof("the machine[%s] is connecting: %s:%d", mi.Name, mi.Ip, mi.Port)
2023-10-30 17:34:56 +08:00
// 如果使用了ssh隧道则修改机器ip port为暴露的ip port
2024-05-21 04:06:13 +00:00
err := mi.IfUseSshTunnelChangeIpPort(false)
2023-10-30 17:34:56 +08:00
if err != nil {
2024-11-20 22:43:53 +08:00
return nil, errorx.NewBiz("ssh tunnel connection failed: %s", err.Error())
2023-10-30 17:34:56 +08:00
}
cli := &Cli{Info: mi}
sshClient, err := GetSshClient(mi, nil)
2023-10-30 17:34:56 +08:00
if err != nil {
if mi.UseSshTunnel() {
CloseSshTunnelMachine(int(mi.TempSshMachineId), mi.GetTunnelId())
2023-10-30 17:34:56 +08:00
}
return nil, err
}
cli.sshClient = sshClient
return cli, nil
}
// 如果使用了ssh隧道则修改机器ip port为暴露的ip port
2024-05-21 04:06:13 +00:00
func (mi *MachineInfo) IfUseSshTunnelChangeIpPort(out bool) error {
if !mi.UseSshTunnel() {
2023-10-30 17:34:56 +08:00
return nil
}
2024-05-21 04:06:13 +00:00
originId := mi.Id
2023-10-30 17:34:56 +08:00
if originId == 0 {
// 随机设置一个id如果使用了隧道则用于临时保存隧道
2024-05-21 04:06:13 +00:00
mi.Id = uint64(time.Now().Nanosecond())
2023-10-30 17:34:56 +08:00
}
2024-05-21 04:06:13 +00:00
sshTunnelMachine, err := GetSshTunnelMachine(int(mi.SshTunnelMachine.Id), func(u uint64) (*MachineInfo, error) {
return mi.SshTunnelMachine, nil
2023-10-30 17:34:56 +08:00
})
if err != nil {
return err
}
2024-05-21 04:06:13 +00:00
exposeIp, exposePort, err := sshTunnelMachine.OpenSshTunnel(mi.GetTunnelId(), mi.Ip, mi.Port)
2023-10-30 17:34:56 +08:00
if err != nil {
return err
}
2024-05-21 04:06:13 +00:00
// 是否获取局域网的本地IP
if out {
exposeIp = netx.GetOutBoundIP()
}
2023-10-30 17:34:56 +08:00
// 修改机器ip地址
2024-05-21 04:06:13 +00:00
mi.Ip = exposeIp
mi.Port = exposePort
// 代理之后置空跳板机信息,防止重复跳
2024-05-21 04:06:13 +00:00
mi.TempSshMachineId = mi.SshTunnelMachine.Id
mi.SshTunnelMachine = nil
2023-10-30 17:34:56 +08:00
return nil
}
func GetSshClient(m *MachineInfo, jumpClient *ssh.Client) (*ssh.Client, error) {
// 递归一直取到底层没有跳板机的机器信息
if m.SshTunnelMachine != nil {
2024-01-29 16:02:28 +08:00
jumpClient, err := GetSshClient(m.SshTunnelMachine, jumpClient)
if err != nil {
return nil, err
}
// 新建一个没有跳板机的机器信息
m1 := &MachineInfo{
Ip: m.Ip,
Port: m.Port,
AuthMethod: m.AuthMethod,
Username: m.Username,
Password: m.Password,
Passphrase: m.Passphrase,
}
// 使用跳板机连接目标机器
return GetSshClient(m1, jumpClient)
}
// 配置 SSH 客户端
2023-10-30 17:34:56 +08:00
config := &ssh.ClientConfig{
User: m.Username,
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
Timeout: 5 * time.Second,
}
if ciphers := m.GetExtraString("ciphers"); ciphers != "" {
config.Ciphers = strings.Split(ciphers, ",")
}
if keyExchanges := m.GetExtraString("keyExchanges"); keyExchanges != "" {
config.KeyExchanges = strings.Split(keyExchanges, ",")
}
if m.AuthMethod == int8(tagentity.AuthCertCiphertextTypePassword) {
2023-10-30 17:34:56 +08:00
config.Auth = []ssh.AuthMethod{ssh.Password(m.Password)}
} else if m.AuthMethod == int8(tagentity.AuthCertCiphertextTypePrivateKey) {
2023-10-30 17:34:56 +08:00
var key ssh.Signer
var err error
if len(m.Passphrase) > 0 {
key, err = ssh.ParsePrivateKeyWithPassphrase([]byte(m.Password), []byte(m.Passphrase))
} else {
key, err = ssh.ParsePrivateKey([]byte(m.Password))
}
if err != nil {
return nil, err
}
config.Auth = []ssh.AuthMethod{ssh.PublicKeys(key)}
}
addr := fmt.Sprintf("%s:%d", m.Ip, m.Port)
if jumpClient != nil {
// 连接目标服务器
netConn, err := jumpClient.Dial("tcp", addr)
if err != nil {
return nil, err
}
conn, channel, reqs, err := ssh.NewClientConn(netConn, addr, config)
if err != nil {
return nil, err
}
// 创建目标服务器的 SSH 客户端
return ssh.NewClient(conn, channel, reqs), nil
}
2023-10-30 17:34:56 +08:00
sshClient, err := ssh.Dial("tcp", addr, config)
if err != nil {
return nil, err
}
return sshClient, nil
}