From 8c9253da80f6a536e0df530f5cb6577d7f85fed0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=A4=A7=E5=9C=A3=E4=B9=8B=E5=AE=B6?= <316014408@qq.com>
Date: Wed, 20 Jul 2022 01:37:25 +0000
Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9Emysql=20ssh=E4=BB=A3?=
=?UTF-8?q?=E7=90=86=E8=BF=9E=E6=8E=A5=E6=96=B9=E5=BC=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
mayfly_go_web/src/views/ops/db/DbEdit.vue | 28 ++++++++++
server/internal/devops/api/db.go | 9 +++
server/internal/devops/api/form/db.go | 6 ++
server/internal/devops/api/vo/db.go | 5 ++
server/internal/devops/application/db_app.go | 33 ++++++++++-
server/internal/devops/domain/entity/db.go | 6 ++
server/mayfly-go.sql | 7 ++-
server/pkg/utils/ssh_client.go | 58 ++++++++++++++++++++
8 files changed, 150 insertions(+), 2 deletions(-)
create mode 100644 server/pkg/utils/ssh_client.go
diff --git a/mayfly_go_web/src/views/ops/db/DbEdit.vue b/mayfly_go_web/src/views/ops/db/DbEdit.vue
index 977d21c4..7c27ce30 100644
--- a/mayfly_go_web/src/views/ops/db/DbEdit.vue
+++ b/mayfly_go_web/src/views/ops/db/DbEdit.vue
@@ -67,6 +67,28 @@
/>
+ 添加数据库
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -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);
diff --git a/server/internal/devops/api/db.go b/server/internal/devops/api/db.go
index 35cad0fa..dda454e2 100644
--- a/server/internal/devops/api/db.go
+++ b/server/internal/devops/api/db.go
@@ -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)
diff --git a/server/internal/devops/api/form/db.go b/server/internal/devops/api/form/db.go
index 241178ba..74abd25a 100644
--- a/server/internal/devops/api/form/db.go
+++ b/server/internal/devops/api/form/db.go
@@ -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 {
diff --git a/server/internal/devops/api/vo/db.go b/server/internal/devops/api/vo/db.go
index 542dae85..79c1466f 100644
--- a/server/internal/devops/api/vo/db.go
+++ b/server/internal/devops/api/vo/db.go
@@ -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"`
}
diff --git a/server/internal/devops/application/db_app.go b/server/internal/devops/application/db_app.go
index 92d5deab..b97da572 100644
--- a/server/internal/devops/application/db_app.go
+++ b/server/internal/devops/application/db_app.go
@@ -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连接
- dbEntity.Network = "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))
diff --git a/server/internal/devops/domain/entity/db.go b/server/internal/devops/domain/entity/db.go
index df95aa00..0ad70fa5 100644
--- a/server/internal/devops/domain/entity/db.go
+++ b/server/internal/devops/domain/entity/db.go
@@ -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:"-"`
}
diff --git a/server/mayfly-go.sql b/server/mayfly-go.sql
index 1fab41ab..82605103 100644
--- a/server/mayfly-go.sql
+++ b/server/mayfly-go.sql
@@ -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='数据库资源信息表';
diff --git a/server/pkg/utils/ssh_client.go b/server/pkg/utils/ssh_client.go
new file mode 100644
index 00000000..8b9cfaf9
--- /dev/null
+++ b/server/pkg/utils/ssh_client.go
@@ -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
+}