feat: 支持openssh加密密钥

This commit is contained in:
chaoszhu
2025-08-09 21:30:52 +08:00
parent e29a0acd3d
commit d9b30554e1
7 changed files with 43 additions and 9 deletions
+13 -2
View File
@@ -1,6 +1,5 @@
## TODO
* 服务器文件对传
* 批量下发后台定时任务
* AI对话收藏
* 移动端Ctrl、Tab的按键面板
@@ -8,7 +7,19 @@
* 进程管理
* 当前会话连接|w
* 日志打印优化
*
* rdp
* 探针接入
## [3.4.0](https://github.com/chaos-zhu/easynode/releases) (2025-08-04)
* 🎉新增文件对传功能
* SFTP面板支持手动断开重连
* SFTP loading ui修复
* 禁用MFA2添加验证
* 支持openssh加密密钥
* 点击Logo切换菜单位置
* 换了个新Logo
* 合并通知配置tab
## [3.3.0](https://github.com/chaos-zhu/easynode/releases) (2025-08-04)
* 🎉新增终端连接代理服务
File diff suppressed because one or more lines are too long
+5 -3
View File
@@ -9,15 +9,16 @@ async function getSSHList({ res }) {
let data = await credentialsDB.findAsync({})
data = data?.map(item => {
const { name, authType, _id: id, date } = item
return { id, name, authType, privateKey: '', password: '', date }
return { id, name, authType, privateKey: '', password: '', openSSHKeyPassword: '', date }
}) || []
data.sort((a, b) => b.date - a.date)
res.success({ data })
}
const addSSH = async ({ res, request }) => {
let { body: { name, authType, password, privateKey, tempKey } } = request
let { body: { name, authType, password, privateKey, openSSHKeyPassword, tempKey } } = request
let record = { name, authType, password, privateKey }
if (openSSHKeyPassword) record.openSSHKeyPassword = openSSHKeyPassword
if (!name || !record[authType]) return res.fail({ data: false, msg: '参数错误' })
let count = await credentialsDB.countAsync({ name })
if (count > 0) return res.fail({ data: false, msg: '已存在同名凭证' })
@@ -34,8 +35,9 @@ const addSSH = async ({ res, request }) => {
}
const updateSSH = async ({ res, request }) => {
let { body: { id, name, authType, password, privateKey, date, tempKey } } = request
let { body: { id, name, authType, password, privateKey, openSSHKeyPassword, date, tempKey } } = request
let record = { name, authType, password, privateKey, date }
if (openSSHKeyPassword) record.openSSHKeyPassword = openSSHKeyPassword
if (!id || !name) return res.fail({ data: false, msg: '请输入凭据名称' })
let oldRecord = await credentialsDB.findOneAsync({ _id: id })
if (!oldRecord) return res.fail({ data: false, msg: '凭证不存在' })
File diff suppressed because one or more lines are too long
+3
View File
@@ -22,6 +22,9 @@ async function getConnectionOptions(hostId) {
const sshRecord = await credentialsDB.findOneAsync({ _id: credentialId })
authInfo.authType = sshRecord.authType
authInfo[authInfo.authType] = await AESDecryptAsync(sshRecord[authInfo.authType])
if (authInfo.authType === 'privateKey' && sshRecord.openSSHKeyPassword) {
authInfo.passphrase = sshRecord.openSSHKeyPassword
}
} else {
authInfo[authType] = await AESDecryptAsync(hostInfo[authType])
}
+1 -1
View File
@@ -1 +1 @@
U2FsdGVkX19cLtuM32n63Jkv0/ObLTjPu6qgg7GgYXu5azvBJiwxIKAZpdoIwwJyxqsRh5oggNeUZxVtLyQ2DPvBV8N+e4P9SCIZ4AwL0NoQzgwes1gjKAR6bfVxCJ1KjB2oSa+HQLtKc9grodpsF++KLNC8+D1ZQBWVQ7Florqr0Zn0Nq2v3tsO2wS22QEGnmtTQaGELMnBgNti1EuBk7XAcChOCIY3KSsELowIdcFyyAlouc/YJiholQELTc3R3li3GSlmOuFPTnnaLe/PBMxjbTApma2otVTl5ddNkKzMrMSyOhBprw1nOVK2k/sHhCcZtoVBp3iT/13Dv50W2YTAw8k9Tb52w1jcn+fDIcj1EBZNOCTUhJ7TJXgncFCvNuRWKwFrx3vHJWW3Qaep/PMRbv+UYfWnU56kXBldOMTeyvIK4GRrm109fSBAf3/Yrt1MquIkt6GLN5x3eF9p/CwSF5ObykkVa4A7mI10wRcY9Le/HyZbcbdkyICA8Y9+ZM9PAOP1rO7W268NJzc//y+tBH9Lh9X7beK6xR0soMkGsboAsGCSQGG8kLSlCtYSN2dWp2GqNrtgrMjqX2STri+/2PTdJ3rQFoABlIl24qLoZsgcsfiEm3MGnTcSCl2sbnJM0MSCdlTCrozmm9h7yFzrdKwxQamtzGd9UYzFzBUqGMmdXj0MxG0Wt8rGJ+ALM5NWJPV9JdXzsVGTMJcuUUole/ZUAnoeOyVPZGw9u2zve1G6DqxTlt7lTdqt+r7YQs8xVqKzNWhrs4JaHN8VC5vpCDAzPxukPfd7spfLqWn97llTP5zR/b4DiM5vwTuWgHgeXp4s3Ii2hgJW7yim8cWk7Q5phP1mvb0PorlSp+4=
U2FsdGVkX1/ZvAPYoB/CtzeytYO1I2b3w9dMsJCC7Ij/bQSMkkrORQSfc2kzqp6ZOuRvrYaEj8/SoMyukViiB14z5DVhX+Pjfm8VjQ3sNtPfN/Kc8nxJkipB8bjWVuiK9VuTxxBVatGqQ+ESDF8Vzq1ygL3NTGz0QqnfMiY3NXecc/PGi/dP99feMPpL/5XmbqMhDz8FG/MlGhuXIjd+Zl2qkxhGgI2bgOwcyJBLCOuugaAVNUUSZIq4rrsJPgnj/kFf6rh1qgXkTMa3dE7XACoNyjnYPUCX0brIenKUqd7aehFKiRczudmzSjrPJXxb3txUbzjWJzhjW0FA0odNQCWog0Qvh+WXDc76QpMOD/Bq/yxmQvKwx8AniTTn8zhYUnwSWO+b4+H6v/kGcY9LSocQhbK7Ef5cfn7SRnTE5fKcjTrLnXQ2sc3j77VQFcJZ9lCQk0WgfZhtR37ZjzDeMkV/og9Kw4hJpluAFti+4cD2vpqF8c8rs45CtzqpIfc1evQPbxKH4kKtM0gWXrMuyQN3bIuLydsSFX95GZahg8PGdJkRngcHOKjjR/VMsAibdaGHuuQ3UlAqehWGJodSO81+ULVz5qt9VeSB1qZkdopy6GAuZsRbXibTbCv1E9CduFCiZQ8+eWpQexf83jieYevTEpQoS4L5DTxrYK7zavS+3qlECcpDbPiQ8Ji6AkmUokbe5Mnm9WSR+WaIY3gp1T0Of5f6GDvxce1HrM2ysWt5LjLd4sOACL8mVzbqXoMKaU5HC69CtaXsOee68GSr0P2V0glojMS2awRImA3BMUCGABn8SfVn45w1vf2jcwnITD4QrKTXeARuvs1vltvRXeoKyXa+HesQkgBidgIyIBk=
+19 -1
View File
@@ -67,6 +67,19 @@
placeholder="-----BEGIN RSA PRIVATE KEY-----"
/>
</el-form-item>
<el-form-item v-if="sshForm.authType === 'privateKey' && showOpenSSHKeyField" label="私钥密码">
<PlusSupportTip>
<el-input
v-model="sshForm.openSSHKeyPassword"
type="password"
:disabled="!isPlusActive"
placeholder="请输入openssh私钥密码"
show-password
autocomplete="off"
clearable
/>
</PlusSupportTip>
</el-form-item>
<el-form-item v-if="sshForm.authType === 'password'" prop="password" label="密码">
<el-input
v-model.trim="sshForm.password"
@@ -129,7 +142,8 @@ const sshForm = reactive({
name: '',
authType: 'privateKey',
privateKey: '',
password: ''
password: '',
openSSHKeyPassword: ''
})
const rules = computed(() => {
@@ -203,6 +217,7 @@ const handleClickUploadBtn = () => {
const keyPasswordVisible = ref(false)
const keyPassword = ref('')
const tempPrivateKey = ref('')
const showOpenSSHKeyField = ref(false)
const handleSelectPrivateKeyFile = (event) => {
let file = event.target.files[0]
@@ -213,6 +228,9 @@ const handleSelectPrivateKeyFile = (event) => {
if (content.includes('ENCRYPTED')) {
tempPrivateKey.value = content
keyPasswordVisible.value = true
} else if (content.includes('OPENSSH PRIVATE KEY')) {
sshForm.privateKey = content
showOpenSSHKeyField.value = true
} else {
sshForm.privateKey = content
}