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-button v-else class="ml5 mt5" size="small" @click="showInputDb"> + 添加数据库 </el-button>
|
||||||
</el-form-item>
|
</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>
|
</el-form>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -127,6 +149,11 @@ export default defineComponent({
|
|||||||
projectId: null,
|
projectId: null,
|
||||||
envId: null,
|
envId: null,
|
||||||
env: null,
|
env: null,
|
||||||
|
enable_ssh: null,
|
||||||
|
ssh_host: null,
|
||||||
|
ssh_user: null,
|
||||||
|
ssh_pass: null,
|
||||||
|
ssh_port: 22,
|
||||||
},
|
},
|
||||||
btnLoading: false,
|
btnLoading: false,
|
||||||
rules: {
|
rules: {
|
||||||
@@ -264,6 +291,7 @@ export default defineComponent({
|
|||||||
if (valid) {
|
if (valid) {
|
||||||
const reqForm = { ...state.form };
|
const reqForm = { ...state.form };
|
||||||
reqForm.password = await RsaEncrypt(reqForm.password);
|
reqForm.password = await RsaEncrypt(reqForm.password);
|
||||||
|
reqForm.ssh_pass = await RsaEncrypt(reqForm.ssh_pass);
|
||||||
dbApi.saveDb.request(reqForm).then(() => {
|
dbApi.saveDb.request(reqForm).then(() => {
|
||||||
ElMessage.success('保存成功');
|
ElMessage.success('保存成功');
|
||||||
emit('val-change', state.form);
|
emit('val-change', state.form);
|
||||||
|
|||||||
@@ -55,6 +55,15 @@ func (d *Db) Save(rc *ctx.ReqCtx) {
|
|||||||
|
|
||||||
// 密码脱敏记录日志
|
// 密码脱敏记录日志
|
||||||
form.Password = "****"
|
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
|
rc.ReqParam = form
|
||||||
|
|
||||||
db.SetBaseInfo(rc.LoginAccount)
|
db.SetBaseInfo(rc.LoginAccount)
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ type DbForm struct {
|
|||||||
Project string `json:"project"`
|
Project string `json:"project"`
|
||||||
Env string `json:"env"`
|
Env string `json:"env"`
|
||||||
EnvId uint64 `binding:"required" json:"envId"`
|
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 {
|
type DbSqlSaveForm struct {
|
||||||
|
|||||||
@@ -19,4 +19,9 @@ type SelectDataDbVO struct {
|
|||||||
CreateTime *time.Time `json:"createTime"`
|
CreateTime *time.Time `json:"createTime"`
|
||||||
Creator *string `json:"creator"`
|
Creator *string `json:"creator"`
|
||||||
CreatorId *int64 `json:"creatorId"`
|
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/global"
|
||||||
"mayfly-go/pkg/model"
|
"mayfly-go/pkg/model"
|
||||||
"mayfly-go/pkg/utils"
|
"mayfly-go/pkg/utils"
|
||||||
|
"net"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-sql-driver/mysql"
|
||||||
_ "github.com/lib/pq"
|
_ "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) {
|
func (d *dbAppImpl) Save(dbEntity *entity.Db) {
|
||||||
// 默认tcp连接
|
// 默认tcp连接
|
||||||
dbEntity.Network = "tcp"
|
if dbEntity.Type == "mysql" && dbEntity.EnableSSH == 1 {
|
||||||
|
dbEntity.Network = "mysql+ssh"
|
||||||
|
} else {
|
||||||
|
dbEntity.Network = "tcp"
|
||||||
|
}
|
||||||
|
|
||||||
// 测试连接
|
// 测试连接
|
||||||
if dbEntity.Password != "" {
|
if dbEntity.Password != "" {
|
||||||
TestConnection(*dbEntity)
|
TestConnection(*dbEntity)
|
||||||
@@ -155,6 +162,18 @@ func (da *dbAppImpl) GetDbInstance(id uint64, db string) *DbInstance {
|
|||||||
biz.IsTrue(strings.Contains(d.Database, db), "未配置该库的操作权限")
|
biz.IsTrue(strings.Contains(d.Database, db), "未配置该库的操作权限")
|
||||||
global.Log.Infof("连接db: %s:%d/%s", d.Host, d.Port, 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
|
d.Database = db
|
||||||
DB, err := sql.Open(d.Type, getDsn(d))
|
DB, err := sql.Open(d.Type, getDsn(d))
|
||||||
@@ -202,6 +221,18 @@ func GetDbInstanceByCache(id string) *DbInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestConnection(d entity.Db) {
|
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]
|
d.Database = strings.Split(d.Database, " ")[0]
|
||||||
DB, err := sql.Open(d.Type, getDsn(&d))
|
DB, err := sql.Open(d.Type, getDsn(&d))
|
||||||
|
|||||||
@@ -20,4 +20,10 @@ type Db struct {
|
|||||||
Project string
|
Project string
|
||||||
EnvId uint64
|
EnvId uint64
|
||||||
Env string
|
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...)',
|
`type` varchar(20) COLLATE utf8mb4_bin NOT NULL COMMENT '数据库实例类型(mysql...)',
|
||||||
`database` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '数据库,空格分割多个数据库',
|
`database` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '数据库,空格分割多个数据库',
|
||||||
`params` varchar(125) 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_id` bigint(20) DEFAULT NULL,
|
||||||
`project` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL,
|
`project` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||||
`env_id` bigint(20) DEFAULT NULL COMMENT '环境id',
|
`env_id` bigint(20) DEFAULT NULL COMMENT '环境id',
|
||||||
@@ -41,6 +41,11 @@ CREATE TABLE `t_db` (
|
|||||||
`update_time` datetime DEFAULT NULL,
|
`update_time` datetime DEFAULT NULL,
|
||||||
`modifier_id` bigint(20) DEFAULT NULL,
|
`modifier_id` bigint(20) DEFAULT NULL,
|
||||||
`modifier` varchar(32) COLLATE utf8mb4_bin 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`)
|
PRIMARY KEY (`id`)
|
||||||
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='数据库资源信息表';
|
) 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