mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-02 23:40:24 +08:00
feat: 新增mysql ssh代理连接方式
This commit is contained in:
@@ -67,6 +67,28 @@
|
||||
/>
|
||||
<el-button v-else class="ml5 mt5" size="small" @click="showInputDb"> + 添加数据库 </el-button>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="enable_ssh" label="SSH:" v-if="form.type === 'mysql'">
|
||||
<el-checkbox v-model="form.enable_ssh" :true-label=1 :false-label=0></el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item prop="ssh_host" label="SSH Host:" v-if="form.enable_ssh === 1 && form.type === 'mysql'">
|
||||
<el-input v-model.trim="form.ssh_host" placeholder="请输入主机ip" auto-complete="off"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="ssh_user" label="SSH User:" v-if="form.enable_ssh === 1 && form.type === 'mysql'">
|
||||
<el-input v-model.trim="form.ssh_user" placeholder="请输入用户名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="ssh_pass" label="SSH Pass:" v-if="form.enable_ssh === 1 && form.type === 'mysql'">
|
||||
<el-input
|
||||
type="password"
|
||||
show-password
|
||||
v-model.trim="form.ssh_pass"
|
||||
placeholder="请输入密码,修改操作可不填"
|
||||
autocomplete="new-password"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="ssh_port" label="SSH Port:" v-if="form.enable_ssh === 1 && form.type === 'mysql'">
|
||||
<el-input type="number" v-model.number="form.ssh_port" placeholder="请输入端口"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
@@ -127,6 +149,11 @@ export default defineComponent({
|
||||
projectId: null,
|
||||
envId: null,
|
||||
env: null,
|
||||
enable_ssh: null,
|
||||
ssh_host: null,
|
||||
ssh_user: null,
|
||||
ssh_pass: null,
|
||||
ssh_port: 22,
|
||||
},
|
||||
btnLoading: false,
|
||||
rules: {
|
||||
@@ -264,6 +291,7 @@ export default defineComponent({
|
||||
if (valid) {
|
||||
const reqForm = { ...state.form };
|
||||
reqForm.password = await RsaEncrypt(reqForm.password);
|
||||
reqForm.ssh_pass = await RsaEncrypt(reqForm.ssh_pass);
|
||||
dbApi.saveDb.request(reqForm).then(() => {
|
||||
ElMessage.success('保存成功');
|
||||
emit('val-change', state.form);
|
||||
|
||||
@@ -55,6 +55,15 @@ func (d *Db) Save(rc *ctx.ReqCtx) {
|
||||
|
||||
// 密码脱敏记录日志
|
||||
form.Password = "****"
|
||||
|
||||
if form.Type == "mysql" && form.EnableSSH == 1 {
|
||||
originSSHPwd, err := utils.DefaultRsaDecrypt(form.SSHPass, true)
|
||||
biz.ErrIsNilAppendErr(err, "解密密码错误: %s")
|
||||
db.SSHPass = originSSHPwd
|
||||
// 密码脱敏记录日志
|
||||
form.SSHPass = "****"
|
||||
}
|
||||
|
||||
rc.ReqParam = form
|
||||
|
||||
db.SetBaseInfo(rc.LoginAccount)
|
||||
|
||||
@@ -14,6 +14,12 @@ type DbForm struct {
|
||||
Project string `json:"project"`
|
||||
Env string `json:"env"`
|
||||
EnvId uint64 `binding:"required" json:"envId"`
|
||||
|
||||
EnableSSH int `json:"enable_ssh"`
|
||||
SSHHost string `json:"ssh_host"`
|
||||
SSHPort int `json:"ssh_port"`
|
||||
SSHUser string `json:"ssh_user"`
|
||||
SSHPass string `json:"ssh_pass"`
|
||||
}
|
||||
|
||||
type DbSqlSaveForm struct {
|
||||
|
||||
@@ -19,4 +19,9 @@ type SelectDataDbVO struct {
|
||||
CreateTime *time.Time `json:"createTime"`
|
||||
Creator *string `json:"creator"`
|
||||
CreatorId *int64 `json:"creatorId"`
|
||||
|
||||
EnableSSH *int `json:"enable_ssh"`
|
||||
SSHHost *string `json:"ssh_host"`
|
||||
SSHPort *int `json:"ssh_port"`
|
||||
SSHUser *string `json:"ssh_user"`
|
||||
}
|
||||
|
||||
@@ -12,12 +12,14 @@ import (
|
||||
"mayfly-go/pkg/global"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/utils"
|
||||
"net"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-sql-driver/mysql"
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
@@ -75,7 +77,12 @@ func (d *dbAppImpl) GetById(id uint64, cols ...string) *entity.Db {
|
||||
|
||||
func (d *dbAppImpl) Save(dbEntity *entity.Db) {
|
||||
// 默认tcp连接
|
||||
if dbEntity.Type == "mysql" && dbEntity.EnableSSH == 1 {
|
||||
dbEntity.Network = "mysql+ssh"
|
||||
} else {
|
||||
dbEntity.Network = "tcp"
|
||||
}
|
||||
|
||||
// 测试连接
|
||||
if dbEntity.Password != "" {
|
||||
TestConnection(*dbEntity)
|
||||
@@ -155,6 +162,18 @@ func (da *dbAppImpl) GetDbInstance(id uint64, db string) *DbInstance {
|
||||
biz.IsTrue(strings.Contains(d.Database, db), "未配置该库的操作权限")
|
||||
global.Log.Infof("连接db: %s:%d/%s", d.Host, d.Port, db)
|
||||
|
||||
//SSH Conect
|
||||
if d.Type == "mysql" && d.EnableSSH == 1 {
|
||||
sshClient, err := utils.SSHConnect(d.SSHUser, d.SSHPass, d.SSHHost, "", d.SSHPort)
|
||||
if err != nil {
|
||||
global.Log.Errorf("ssh连接失败: %s@%s:%d", d.SSHUser, d.SSHHost, d.SSHPort)
|
||||
panic(biz.NewBizErr(fmt.Sprintf("ssh连接失败: %s", err.Error())))
|
||||
}
|
||||
mysql.RegisterDial("mysql+ssh", func(addr string) (net.Conn, error) {
|
||||
return sshClient.Dial("tcp", addr)
|
||||
})
|
||||
}
|
||||
|
||||
// 将数据库替换为要访问的数据库,原本数据库为空格拼接的所有库
|
||||
d.Database = db
|
||||
DB, err := sql.Open(d.Type, getDsn(d))
|
||||
@@ -202,6 +221,18 @@ func GetDbInstanceByCache(id string) *DbInstance {
|
||||
}
|
||||
|
||||
func TestConnection(d entity.Db) {
|
||||
//SSH Conect
|
||||
if d.Type == "mysql" && d.EnableSSH == 1 {
|
||||
sshClient, err := utils.SSHConnect(d.SSHUser, d.SSHPass, d.SSHHost, "", d.SSHPort)
|
||||
if err != nil {
|
||||
global.Log.Errorf("ssh连接失败: %s@%s:%d", d.SSHUser, d.SSHHost, d.SSHPort)
|
||||
panic(biz.NewBizErr(fmt.Sprintf("ssh连接失败: %s", err.Error())))
|
||||
}
|
||||
mysql.RegisterDial("mysql+ssh", func(addr string) (net.Conn, error) {
|
||||
return sshClient.Dial("tcp", addr)
|
||||
})
|
||||
}
|
||||
|
||||
// 验证第一个库是否可以连接即可
|
||||
d.Database = strings.Split(d.Database, " ")[0]
|
||||
DB, err := sql.Open(d.Type, getDsn(&d))
|
||||
|
||||
@@ -20,4 +20,10 @@ type Db struct {
|
||||
Project string
|
||||
EnvId uint64
|
||||
Env string
|
||||
|
||||
EnableSSH int `orm:"column(enable_ssh)" json:"enable_ssh"`
|
||||
SSHHost string `orm:"column(ssh_host)" json:"ssh_host"`
|
||||
SSHPort int `orm:"column(ssh_port)" json:"ssh_port"`
|
||||
SSHUser string `orm:"column(ssh_user)" json:"ssh_user"`
|
||||
SSHPass string `orm:"column(ssh_pass)" json:"-"`
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ CREATE TABLE `t_db` (
|
||||
`type` varchar(20) COLLATE utf8mb4_bin NOT NULL COMMENT '数据库实例类型(mysql...)',
|
||||
`database` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '数据库,空格分割多个数据库',
|
||||
`params` varchar(125) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '其他连接参数',
|
||||
`network` varchar(8) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`network` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`project_id` bigint(20) DEFAULT NULL,
|
||||
`project` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`env_id` bigint(20) DEFAULT NULL COMMENT '环境id',
|
||||
@@ -41,6 +41,11 @@ CREATE TABLE `t_db` (
|
||||
`update_time` datetime DEFAULT NULL,
|
||||
`modifier_id` bigint(20) DEFAULT NULL,
|
||||
`modifier` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`enable_ssh` tinyint(1) unsigned NOT NULL DEFAULT '0',
|
||||
`ssh_host` varchar(50) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
|
||||
`ssh_port` int(8) NOT NULL,
|
||||
`ssh_user` varchar(255) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
|
||||
`ssh_pass` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='数据库资源信息表';
|
||||
|
||||
|
||||
58
server/pkg/utils/ssh_client.go
Normal file
58
server/pkg/utils/ssh_client.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func SSHConnect(user, password, host, key string, port int) (*ssh.Client, error) {
|
||||
var (
|
||||
auth []ssh.AuthMethod
|
||||
addr string
|
||||
clientConfig *ssh.ClientConfig
|
||||
client *ssh.Client
|
||||
config ssh.Config
|
||||
//session *ssh.Session
|
||||
err error
|
||||
)
|
||||
|
||||
// get auth method
|
||||
auth = make([]ssh.AuthMethod, 0)
|
||||
if key == "" {
|
||||
auth = append(auth, ssh.Password(password))
|
||||
} else {
|
||||
pemBytes, err := ioutil.ReadFile(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var signer ssh.Signer
|
||||
if password == "" {
|
||||
signer, err = ssh.ParsePrivateKey(pemBytes)
|
||||
} else {
|
||||
signer, err = ssh.ParsePrivateKeyWithPassphrase(pemBytes, []byte(password))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
auth = append(auth, ssh.PublicKeys(signer))
|
||||
}
|
||||
|
||||
clientConfig = &ssh.ClientConfig{
|
||||
User: user,
|
||||
Auth: auth,
|
||||
Timeout: 30 * time.Second,
|
||||
Config: config,
|
||||
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
addr = fmt.Sprintf("%s:%d", host, port)
|
||||
|
||||
client, err = ssh.Dial("tcp", addr, clientConfig)
|
||||
return client, err
|
||||
}
|
||||
Reference in New Issue
Block a user