fix: 问题修复与redis密码迁移至凭证

This commit is contained in:
meilin.huang
2024-04-13 17:01:12 +08:00
parent 1a4626c24d
commit f4162c38db
13 changed files with 139 additions and 120 deletions

View File

@@ -100,9 +100,6 @@ export function backEndRouterConverter(routes: any, callbackFunc: RouterConvCall
}
// 将json字符串的meta转为对象
item.meta = JSON.parse(item.meta);
if (item.meta.isHide) {
continue;
}
// 将meta.comoponet 解析为route.component
if (item.meta.component) {

View File

@@ -73,34 +73,17 @@
<el-input v-model="form.remark" auto-complete="off" type="textarea"></el-input>
</el-form-item>
<template v-if="form.type !== DbType.sqlite">
<el-divider content-position="left">账号</el-divider>
<div>
<ResourceAuthCertTableEdit
v-model="form.authCerts"
:resource-code="form.code"
:resource-type="TagResourceTypeEnum.Db.value"
:test-conn-btn-loading="testConnBtnLoading"
@test-conn="testConn"
:disable-ciphertext-type="[AuthCertCiphertextTypeEnum.PrivateKey.value]"
/>
</div>
</template>
<!--
<el-form-item v-if="form.type !== DbType.sqlite" prop="username" label="用户名" required>
<el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item v-if="form.type !== DbType.sqlite" prop="password" label="密码">
<el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码" autocomplete="new-password">
<template v-if="form.id && form.id != 0" #suffix>
<el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click" :content="pwd">
<template #reference>
<el-link v-auth="'db:instance:save'" @click="getDbPwd" :underline="false" type="primary" class="mr5">原密码 </el-link>
</template>
</el-popover>
</template>
</el-input>
</el-form-item> -->
<el-divider content-position="left">账号</el-divider>
<div>
<ResourceAuthCertTableEdit
v-model="form.authCerts"
:resource-code="form.code"
:resource-type="TagResourceTypeEnum.Db.value"
:test-conn-btn-loading="testConnBtnLoading"
@test-conn="testConn"
:disable-ciphertext-type="[AuthCertCiphertextTypeEnum.PrivateKey.value]"
/>
</div>
<el-divider content-position="left">其他</el-divider>
<el-form-item prop="params" label="连接参数">

View File

@@ -57,14 +57,15 @@
v-model.trim="form.password"
placeholder="请输入密码, 修改操作可不填"
autocomplete="new-password"
><template v-if="form.id && form.id != 0" #suffix>
>
<!-- <template v-if="form.id && form.id != 0" #suffix>
<el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click" :content="pwd">
<template #reference>
<el-link @click="getPwd" :underline="false" type="primary" class="mr5">原密码</el-link>
</template>
</el-popover>
</template></el-input
>
</template> -->
</el-input>
</el-form-item>
<el-form-item prop="db" label="库号" required>
<el-select
@@ -110,7 +111,6 @@
import { toRefs, reactive, watch, ref } from 'vue';
import { redisApi } from './api';
import { ElMessage } from 'element-plus';
import { RsaEncrypt } from '@/common/rsa';
import TagTreeSelect from '../component/TagTreeSelect.vue';
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
import ProcdefSelectFormItem from '@/views/flow/components/ProcdefSelectFormItem.vue';
@@ -206,7 +206,7 @@ const state = reactive({
pwd: '',
});
const { dialogVisible, tabActiveName, form, submitForm, dbList, pwd } = toRefs(state);
const { dialogVisible, tabActiveName, form, submitForm, dbList } = toRefs(state);
const { isFetching: testConnBtnLoading, execute: testConnExec } = redisApi.testConn.useApi(submitForm);
const { isFetching: saveBtnLoading, execute: saveRedisExec } = redisApi.saveRedis.useApi(submitForm);
@@ -238,10 +238,6 @@ const changeDb = () => {
state.form.db = state.dbList.length == 0 ? '' : state.dbList.join(',');
};
const getPwd = async () => {
state.pwd = await redisApi.getRedisPwd.request({ id: state.form.id });
};
const getReqForm = async () => {
const reqForm = { ...state.form };
if (reqForm.mode == 'sentinel' && reqForm.host.split('=').length != 2) {
@@ -251,7 +247,6 @@ const getReqForm = async () => {
if (!state.form.sshTunnelMachineId || state.form.sshTunnelMachineId <= 0) {
reqForm.sshTunnelMachineId = -1;
}
reqForm.password = await RsaEncrypt(reqForm.password);
return reqForm;
};

View File

@@ -140,7 +140,7 @@
</el-dialog>
<redis-edit
@val-change="search"
@val-change="search()"
:title="redisEditDialog.title"
v-model:visible="redisEditDialog.visible"
v-model:redis="redisEditDialog.data"

View File

@@ -8,7 +8,7 @@ type DbForm struct {
Remark string `json:"remark"`
TagId []uint64 `binding:"required" json:"tagId"`
InstanceId uint64 `binding:"required" json:"instanceId"`
AuthCertName string `json: "authCertName`
AuthCertName string `json: "authCertName"`
FlowProcdefKey string `json:"flowProcdefKey"`
}

View File

@@ -92,7 +92,7 @@ func (d *dbSqlExecAppImpl) Exec(ctx context.Context, execSqlReq *DbSqlExecReq) (
// 就算解析失败也执行sql让数据库来判断错误。如果是查询sql则简单判断是否有limit分页参数信息兼容pgsql
// logx.Warnf("sqlparse解析sql[%s]失败: %s", sql, err.Error())
lowerSql := strings.ToLower(execSqlReq.Sql)
isSelect := strings.HasPrefix(lowerSql, "select")
isSelect := strings.HasPrefix(lowerSql, "select") || strings.HasPrefix(lowerSql, "explain")
if isSelect {
// 如果配置为0则不校验分页参数
maxCount := config.GetDbms().MaxResultSet
@@ -129,6 +129,9 @@ func (d *dbSqlExecAppImpl) Exec(ctx context.Context, execSqlReq *DbSqlExecReq) (
case *sqlparser.Select:
isSelect = true
execRes, err = d.doSelect(ctx, stmt, execSqlReq)
case *sqlparser.ExplainStmt:
isSelect = true
execRes, err = d.doRead(ctx, execSqlReq)
case *sqlparser.Show:
isSelect = true
execRes, err = d.doRead(ctx, execSqlReq)

View File

@@ -2,6 +2,7 @@ package api
import (
"context"
"fmt"
"mayfly-go/internal/common/consts"
"mayfly-go/internal/redis/api/form"
"mayfly-go/internal/redis/api/vo"
@@ -14,7 +15,6 @@ import (
"mayfly-go/pkg/model"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx"
"mayfly-go/pkg/utils/cryptox"
"mayfly-go/pkg/utils/stringx"
"strconv"
"strings"
@@ -54,39 +54,40 @@ func (r *Redis) TestConn(rc *req.Ctx) {
form := &form.Redis{}
redis := req.BindJsonAndCopyTo[*entity.Redis](rc, form, new(entity.Redis))
// 密码解密,并使用解密后的赋值
originPwd, err := cryptox.DefaultRsaDecrypt(redis.Password, true)
biz.ErrIsNilAppendErr(err, "解密密码错误: %s")
redis.Password = originPwd
biz.ErrIsNil(r.RedisApp.TestConn(redis))
biz.ErrIsNil(r.RedisApp.TestConn(&application.SaveRedisParam{
Redis: redis,
TagIds: form.TagId,
AuthCert: &tagentity.ResourceAuthCert{
Name: fmt.Sprintf("redis_%s_ac", redis.Code),
Username: form.Username,
Ciphertext: form.Password,
CiphertextType: tagentity.AuthCertCiphertextTypePassword,
Type: tagentity.AuthCertTypePrivate,
},
}))
}
func (r *Redis) Save(rc *req.Ctx) {
form := &form.Redis{}
redis := req.BindJsonAndCopyTo[*entity.Redis](rc, form, new(entity.Redis))
// 密码解密,并使用解密后的赋值
originPwd, err := cryptox.DefaultRsaDecrypt(redis.Password, true)
biz.ErrIsNilAppendErr(err, "解密密码错误: %s")
redis.Password = originPwd
// 密码脱敏记录日志
form.Password = "****"
rc.ReqParam = form
biz.ErrIsNil(r.RedisApp.SaveRedis(rc.MetaCtx, redis, form.TagId...))
}
// 获取redis实例密码由于数据库是加密存储故提供该接口展示原文密码
func (r *Redis) GetRedisPwd(rc *req.Ctx) {
rid := uint64(rc.PathParamInt("id"))
re, err := r.RedisApp.GetById(new(entity.Redis), rid, "Password")
biz.ErrIsNil(err, "redis信息不存在")
if err := re.PwdDecrypt(); err != nil {
biz.ErrIsNil(err)
redisParam := &application.SaveRedisParam{
Redis: redis,
TagIds: form.TagId,
AuthCert: &tagentity.ResourceAuthCert{
Name: fmt.Sprintf("redis_%s_ac", redis.Code),
Username: form.Username,
Ciphertext: form.Password,
CiphertextType: tagentity.AuthCertCiphertextTypePassword,
Type: tagentity.AuthCertTypePrivate,
},
}
rc.ResData = re.Password
biz.ErrIsNil(r.RedisApp.SaveRedis(rc.MetaCtx, redisParam))
}
func (r *Redis) DeleteRedis(rc *req.Ctx) {

View File

@@ -9,7 +9,7 @@ import (
"mayfly-go/internal/redis/domain/repository"
"mayfly-go/internal/redis/rdm"
tagapp "mayfly-go/internal/tag/application"
tagenttiy "mayfly-go/internal/tag/domain/entity"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/base"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/logx"
@@ -19,9 +19,16 @@ import (
"strconv"
"strings"
"github.com/may-fly/cast"
"github.com/redis/go-redis/v9"
)
type SaveRedisParam struct {
Redis *entity.Redis
AuthCert *tagentity.ResourceAuthCert
TagIds []uint64
}
type RunCmdParam struct {
Id uint64 `json:"id"`
Db int `json:"db"`
@@ -37,9 +44,9 @@ type Redis interface {
GetPageList(condition *entity.RedisQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
// 测试连接
TestConn(re *entity.Redis) error
TestConn(re *SaveRedisParam) error
SaveRedis(ctx context.Context, re *entity.Redis, tagIds ...uint64) error
SaveRedis(ctx context.Context, param *SaveRedisParam) error
// 删除数据库信息
Delete(ctx context.Context, id uint64) error
@@ -56,8 +63,9 @@ type Redis interface {
type redisAppImpl struct {
base.AppImpl[*entity.Redis, repository.Redis]
tagApp tagapp.TagTree `inject:"TagTreeApp"`
procinstApp flowapp.Procinst `inject:"ProcinstApp"`
tagApp tagapp.TagTree `inject:"TagTreeApp"`
procinstApp flowapp.Procinst `inject:"ProcinstApp"`
resourceAuthCertApp tagapp.ResourceAuthCert `inject:"ResourceAuthCertApp"`
}
// 注入RedisRepo
@@ -70,13 +78,28 @@ func (r *redisAppImpl) GetPageList(condition *entity.RedisQuery, pageParam *mode
return r.GetRepo().GetRedisList(condition, pageParam, toEntity, orderBy...)
}
func (r *redisAppImpl) TestConn(re *entity.Redis) error {
func (r *redisAppImpl) TestConn(param *SaveRedisParam) error {
db := 0
re := param.Redis
if re.Db != "" {
db, _ = strconv.Atoi(strings.Split(re.Db, ",")[0])
db = cast.ToInt(strings.Split(re.Db, ",")[0])
}
rc, err := re.ToRedisInfo(db).Conn()
authCert := param.AuthCert
if authCert.Id != 0 {
// 密文可能被清除,故需要重新获取
authCert, _ = r.resourceAuthCertApp.GetAuthCert(authCert.Name)
} else {
if authCert.CiphertextType == tagentity.AuthCertCiphertextTypePublic {
publicAuthCert, err := r.resourceAuthCertApp.GetAuthCert(authCert.Ciphertext)
if err != nil {
return err
}
authCert = publicAuthCert
}
}
rc, err := re.ToRedisInfo(db, authCert).Conn()
if err != nil {
return err
}
@@ -84,7 +107,9 @@ func (r *redisAppImpl) TestConn(re *entity.Redis) error {
return nil
}
func (r *redisAppImpl) SaveRedis(ctx context.Context, re *entity.Redis, tagIds ...uint64) error {
func (r *redisAppImpl) SaveRedis(ctx context.Context, param *SaveRedisParam) error {
re := param.Redis
tagIds := param.TagIds
// 查找是否存在该库
oldRedis := &entity.Redis{
Host: re.Host,
@@ -100,18 +125,20 @@ func (r *redisAppImpl) SaveRedis(ctx context.Context, re *entity.Redis, tagIds .
return errorx.NewBiz("该编码已存在")
}
if errEnc := re.PwdEncrypt(); errEnc != nil {
return errorx.NewBiz(errEnc.Error())
}
return r.Tx(ctx, func(ctx context.Context) error {
return r.Insert(ctx, re)
}, func(ctx context.Context) error {
return r.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
Type: tagenttiy.TagTypeRedis,
Type: tagentity.TagTypeRedis,
Code: re.Code,
ParentTagIds: tagIds,
})
}, func(ctx context.Context) error {
return r.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{
ResourceCode: re.Code,
ResourceType: tagentity.TagTypeRedis,
AuthCerts: []*tagentity.ResourceAuthCert{param.AuthCert},
})
})
}
@@ -131,18 +158,21 @@ func (r *redisAppImpl) SaveRedis(ctx context.Context, re *entity.Redis, tagIds .
oldRedis, _ = r.GetById(new(entity.Redis), re.Id)
}
if errEnc := re.PwdEncrypt(); errEnc != nil {
return errorx.NewBiz(errEnc.Error())
}
re.Code = ""
return r.Tx(ctx, func(ctx context.Context) error {
return r.UpdateById(ctx, re)
}, func(ctx context.Context) error {
return r.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
Type: tagenttiy.TagTypeRedis,
Type: tagentity.TagTypeRedis,
Code: oldRedis.Code,
ParentTagIds: tagIds,
})
}, func(ctx context.Context) error {
return r.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{
ResourceCode: oldRedis.Code,
ResourceType: tagentity.TagTypeRedis,
AuthCerts: []*tagentity.ResourceAuthCert{param.AuthCert},
})
})
}
@@ -162,9 +192,14 @@ func (r *redisAppImpl) Delete(ctx context.Context, id uint64) error {
return r.DeleteById(ctx, id)
}, func(ctx context.Context) error {
return r.tagApp.SaveResourceTag(ctx, &tagapp.SaveResourceTagParam{
Type: tagenttiy.TagTypeRedis,
Type: tagentity.TagTypeRedis,
Code: re.Code,
})
}, func(ctx context.Context) error {
return r.resourceAuthCertApp.RelateAuthCert(ctx, &tagapp.RelateAuthCertParam{
ResourceCode: re.Code,
ResourceType: tagentity.TagTypeRedis,
})
})
}
@@ -176,10 +211,11 @@ func (r *redisAppImpl) GetRedisConn(id uint64, db int) (*rdm.RedisConn, error) {
if err != nil {
return nil, errorx.NewBiz("redis信息不存在")
}
if err := re.PwdDecrypt(); err != nil {
return nil, errorx.NewBiz(err.Error())
authCert, err := r.resourceAuthCertApp.GetResourceAuthCert(tagentity.TagTypeRedis, re.Code)
if err != nil {
return nil, err
}
return re.ToRedisInfo(db, r.tagApp.ListTagPathByTypeAndCode(consts.ResourceTypeRedis, re.Code)...), nil
return re.ToRedisInfo(db, authCert, r.tagApp.ListTagPathByTypeAndCode(consts.ResourceTypeRedis, re.Code)...), nil
})
}

View File

@@ -1,9 +1,8 @@
package entity
import (
"errors"
"mayfly-go/internal/common/utils"
"mayfly-go/internal/redis/rdm"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils/structx"
)
@@ -15,38 +14,18 @@ type Redis struct {
Name string `orm:"column(name)" json:"name"`
Host string `orm:"column(host)" json:"host"`
Mode string `json:"mode"`
Username string `json:"username"`
Password string `orm:"column(password)" json:"-"`
Db string `orm:"column(database)" json:"db"`
SshTunnelMachineId int `orm:"column(ssh_tunnel_machine_id)" json:"sshTunnelMachineId"` // ssh隧道机器id
Remark string
FlowProcdefKey *string `json:"flowProcdefKey"` // 审批流-流程定义key有值则说明关键操作需要进行审批执行,使用指针为了方便更新空字符串(取消流程审批)
}
func (r *Redis) PwdEncrypt() error {
// 密码替换为加密后的密码
password, err := utils.PwdAesEncrypt(r.Password)
if err != nil {
return errors.New("加密 Redis 密码失败")
}
r.Password = password
return nil
}
func (r *Redis) PwdDecrypt() error {
// 密码替换为解密后的密码
password, err := utils.PwdAesDecrypt(r.Password)
if err != nil {
return errors.New("解密 Redis 密码失败")
}
r.Password = password
return nil
}
// ToRedisInfo 转换为redisInfo进行连接
func (r *Redis) ToRedisInfo(db int, tagPath ...string) *rdm.RedisInfo {
func (r *Redis) ToRedisInfo(db int, authCert *tagentity.ResourceAuthCert, tagPath ...string) *rdm.RedisInfo {
redisInfo := new(rdm.RedisInfo)
_ = structx.Copy(redisInfo, r)
redisInfo.Username = authCert.Username
redisInfo.Password = authCert.Ciphertext
redisInfo.Db = db
redisInfo.TagPath = tagPath
return redisInfo

View File

@@ -28,8 +28,6 @@ func InitRedisRouter(router *gin.RouterGroup) {
req.NewPost("", rs.Save).Log(req.NewLogSave("redis-保存信息")),
req.NewGet(":id/pwd", rs.GetRedisPwd),
req.NewDelete(":id", rs.DeleteRedis).Log(req.NewLogSave("redis-删除信息")),
req.NewGet("/:id/info", rs.RedisInfo),

View File

@@ -71,10 +71,10 @@ func (r *resourceAuthCertAppImpl) RelateAuthCert(ctx context.Context, params *Re
resourceAuthCerts := params.AuthCerts
if resourceCode == "" {
return errorx.NewBiz("资源授权凭证的资源编号不能为空")
return errorx.NewBiz("授权凭证的资源编号不能为空")
}
if resourceType == 0 {
return errorx.NewBiz("资源类型不能为空")
return errorx.NewBiz("授权凭证的资源类型不能为空")
}
// 删除授权信息
@@ -413,6 +413,11 @@ func (r *resourceAuthCertAppImpl) updateAuthCert(ctx context.Context, rac *entit
}
}
// 密文存的不是公共授权凭证名,则进行密文加密处理
if rac.CiphertextType != entity.AuthCertCiphertextTypePublic {
rac.CiphertextEncrypt()
}
// 防止误更新
rac.Name = ""
rac.ResourceCode = ""

View File

@@ -532,8 +532,6 @@ CREATE TABLE `t_redis` (
`code` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'code',
`name` varchar(255) DEFAULT NULL COMMENT '名称',
`host` varchar(255) NOT NULL,
`username` varchar(32) DEFAULT NULL COMMENT '用户名',
`password` varchar(100) DEFAULT NULL,
`db` varchar(64) DEFAULT NULL COMMENT '库号: 多个库用,分割',
`mode` varchar(32) DEFAULT NULL,
`ssh_tunnel_machine_id` bigint(20) DEFAULT NULL COMMENT 'ssh隧道的机器id',

View File

@@ -221,3 +221,27 @@ INSERT INTO `t_sys_resource` (`id`, `pid`, `ui_path`, `type`, `status`, `name`,
INSERT INTO `t_sys_resource` (`id`, `pid`, `ui_path`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `is_deleted`, `delete_time`) VALUES(1712717337, 1712717290, 'tLb8TKLB/m2abQkA8/', 2, 1, '授权凭证密文查看', 'authcert:showciphertext', 1712717337, 'null', 1, 'admin', 1, 'admin', '2024-04-10 10:48:58', '2024-04-10 10:48:58', 0, NULL);
commit;
begin;
-- 迁移redis账号密码
INSERT INTO t_resource_auth_cert ( NAME, resource_code, resource_type, type, ciphertext, ciphertext_type, create_time, creator_id, creator, update_time, modifier_id, modifier, is_deleted )
SELECT
CONCAT('redis_', CODE, '_pwd' ),
CODE,
3,
1,
PASSWORD,
1,
DATE_FORMAT( NOW(), '%Y-%m-%d %H:%i:%s' ),
1,
'admin',
DATE_FORMAT( NOW(), '%Y-%m-%d %H:%i:%s' ),
1,
'admin',
0
FROM
t_redis
WHERE
is_deleted = 0;
ALTER TABLE t_redis DROP COLUMN password;
commit;