ssh 私钥支持配置文件和加载用户目录下的私钥文件

This commit is contained in:
bwcx_jzy 2021-12-24 09:02:31 +08:00
parent 0c7315ee11
commit ad3c347357
No known key found for this signature in database
GPG Key ID: 5E48E9372088B9E5
6 changed files with 77 additions and 15 deletions

View File

@ -19,6 +19,7 @@
5. 【server】项目搜索菜单名变更为项目列表
6. 【server】调整自动清理日志数据逻辑、默认保留日志数据条数修改为 `10000`
7. 【server】脚本模版在服务端统一查看、编辑、执行感谢@ʟᴊx
8. 【server】ssh 私钥支持配置文件和加载用户目录下的私钥文件
> 注意:
> 1. 已经添加的用户重新绑定工作空间权限(默认没有工作空间操作权限)

View File

@ -1,9 +1,11 @@
package io.jpom.controller.node.ssh;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.text.StrSplitter;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.db.Entity;
import cn.hutool.extra.ssh.JschUtil;
import cn.jiangzeyin.common.DefaultSystemLog;
@ -107,7 +109,7 @@ public class SshController extends BaseServerController {
if (connectType == SshModel.ConnectType.PASS) {
Assert.hasText(password, "请填写登录密码");
} else if (connectType == SshModel.ConnectType.PUBKEY) {
Assert.hasText(privateKey, "请填写证书内容");
//Assert.hasText(privateKey, "请填写证书内容");
}
sshModel = new SshModel();
} else {
@ -126,6 +128,10 @@ public class SshController extends BaseServerController {
if (StrUtil.isNotEmpty(password)) {
sshModel.setPassword(password);
}
if (StrUtil.startWith(privateKey, URLUtil.FILE_URL_PREFIX)) {
String rsaPath = StrUtil.removePrefix(privateKey, URLUtil.FILE_URL_PREFIX);
Assert.state(FileUtil.isFile(rsaPath), "配置的私钥文件不存在");
}
if (StrUtil.isNotEmpty(privateKey)) {
sshModel.setPrivateKey(privateKey);
}

View File

@ -6,10 +6,7 @@ import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.LineHandler;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.*;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.extra.ssh.ChannelType;
import cn.hutool.extra.ssh.JschUtil;
@ -21,6 +18,7 @@ import io.jpom.service.h2db.BaseWorkspaceService;
import io.jpom.system.ConfigBean;
import io.jpom.system.ServerExtConfigBean;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import java.io.*;
import java.nio.charset.Charset;
@ -70,15 +68,33 @@ public class SshService extends BaseWorkspaceService<SshModel> {
session = JschUtil.openSession(sshModel.getHost(), sshModel.getPort(), sshModel.getUser(), sshModel.getPassword());
} else if (connectType == SshModel.ConnectType.PUBKEY) {
File tempPath = ConfigBean.getInstance().getTempPath();
String sshFile = StrUtil.emptyToDefault(sshModel.getId(), IdUtil.fastSimpleUUID());
File ssh = FileUtil.file(tempPath, "ssh", sshFile);
FileUtil.writeString(sshModel.getPrivateKey(), ssh, CharsetUtil.UTF_8);
File rsaFile;
String privateKey = sshModel.getPrivateKey();
if (StrUtil.startWith(privateKey, URLUtil.FILE_URL_PREFIX)) {
String rsaPath = StrUtil.removePrefix(privateKey, URLUtil.FILE_URL_PREFIX);
rsaFile = FileUtil.file(rsaPath);
} else if (StrUtil.isEmpty(privateKey)) {
File home = FileUtil.getUserHomeDir();
Assert.notNull(home, "用户目录没有找到");
File identity = FileUtil.file(home, ".ssh", "identity");
rsaFile = FileUtil.isFile(identity) ? identity : null;
File idRsa = FileUtil.file(home, ".ssh", "id_rsa");
rsaFile = FileUtil.isFile(idRsa) ? idRsa : rsaFile;
File idDsa = FileUtil.file(home, ".ssh", "id_dsa");
rsaFile = FileUtil.isFile(idDsa) ? idDsa : rsaFile;
Assert.notNull(rsaFile, "用户目录没有找到私钥信息");
} else {
File tempPath = ConfigBean.getInstance().getTempPath();
String sshFile = StrUtil.emptyToDefault(sshModel.getId(), IdUtil.fastSimpleUUID());
rsaFile = FileUtil.file(tempPath, "ssh", sshFile);
FileUtil.writeString(privateKey, rsaFile, CharsetUtil.UTF_8);
}
Assert.state(FileUtil.isFile(rsaFile), "私钥文件不存在:" + FileUtil.getAbsolutePath(rsaFile));
byte[] pas = null;
if (StrUtil.isNotEmpty(sshModel.getPassword())) {
pas = sshModel.getPassword().getBytes();
}
session = JschUtil.openSession(sshModel.getHost(), sshModel.getPort(), sshModel.getUser(), FileUtil.getAbsolutePath(ssh), pas);
session = JschUtil.openSession(sshModel.getHost(), sshModel.getPort(), sshModel.getUser(), FileUtil.getAbsolutePath(rsaFile), pas);
} else {
throw new IllegalArgumentException("不支持的模式");
}

View File

@ -8,10 +8,26 @@
<a-tooltip title="按住 Ctr 或者 Alt 键点击按钮快速回到第一页">
<a-button type="primary" @click="loadData">搜索</a-button>
</a-tooltip>
<a-space>
<a-tooltip>
<template slot="title">
<div>脚本模版是存储在节点中的命令脚本用于在线管理一些脚本命令如果初始化软件环境管理应用程序等</div>
<a-tooltip placement="topLeft" title="清除服务端缓存节点所有的脚步模版信息, 需要重新同步">
<a-icon @click="delAll()" type="delete" />
</a-tooltip>
<div>
<ul>
<li>执行时候默认不加载全部环境变量需要脚本里面自行加载</li>
<li>命令文件将在 ${jpom插件端数据目录}/script/xxxx.sh 执行</li>
<li>添加脚本模版需要到节点管理中去添加</li>
</ul>
</div>
</template>
<a-icon type="question-circle" theme="filled" />
</a-tooltip>
<a-tooltip placement="topLeft" title="清除服务端缓存节点所有的脚步模版信息, 需要重新同步">
<a-icon @click="delAll()" type="delete" />
</a-tooltip>
</a-space>
</div>
<!-- 数据表格 -->
<a-table :data-source="list" :loading="loading" :columns="columns" @change="changePage" :pagination="pagination" bordered rowKey="id">

View File

@ -79,8 +79,16 @@
<a-form-model-item label="Password" :prop="`${temp.type === 'add' && temp.connectType === 'PASS' ? 'password' : 'password-update'}`">
<a-input-password v-model="temp.password" :placeholder="`${temp.type === 'add' ? '密码' : '密码若没修改可以不用填写'}`" />
</a-form-model-item>
<a-form-model-item v-if="temp.connectType === 'PUBKEY'" label="私钥内容" :prop="`${temp.type === 'add' ? 'privateKey' : ''}`">
<a-textarea v-model="temp.privateKey" :auto-size="{ minRows: 3, maxRows: 5 }" placeholder="私钥内容" />
<a-form-model-item v-if="temp.connectType === 'PUBKEY'" :prop="`${temp.type === 'add' ? 'privateKey' : ''}`">
<template slot="label">
私钥内容
<a-tooltip v-if="temp.type !== 'edit'" placement="topLeft">
<template slot="title">不填将使用默认的 $HOME/.ssh 目录中的配置,使用优先级是id_dsa>id_rsa>identity </template>
<a-icon type="question-circle" theme="filled" />
</a-tooltip>
</template>
<a-textarea v-model="temp.privateKey" :auto-size="{ minRows: 3, maxRows: 5 }" placeholder="私钥内容,不填将使用默认的 $HOME/.ssh 目录中的配置。支持配置文件目录:file:/xxxx/xx" />
</a-form-model-item>
<a-form-model-item label="编码格式" prop="charset">
<a-input v-model="temp.charset" placeholder="编码格式" />

View File

@ -2,10 +2,25 @@
<div class="full-content">
<div ref="filter" class="filter">
<a-input v-model="listQuery['%name%']" placeholder="搜索命令" class="search-input-item" />
<a-input v-model="listQuery['%desc%']" placeholder="描述" class="search-input-item" />
<a-tooltip title="按住 Ctr 或者 Alt 键点击按钮快速回到第一页">
<a-button type="primary" @click="getCommandData">搜索</a-button>
</a-tooltip>
<a-button type="primary" @click="createCommand">新建命令</a-button>
<a-tooltip>
<template slot="title">
<div>命令模版是用于在线管理一些脚本命令如果初始化软件环境管理应用程序等</div>
<div>
<ul>
<li>命令内容支持工作空间环境变量</li>
<li>执行命令将自动替换为 sh 命令文件并自动加载环境变量/etc/profile/etc/bashrc~/.bashrc、~/.bash_profile</li>
<li>命令文件将上传至 ${user.home}/.jpom/xxxx.sh 执行完成将自动删除</li>
</ul>
</div>
</template>
<a-icon type="question-circle" theme="filled" />
</a-tooltip>
</div>
<a-table :loading="loading" :data-source="commandList" :columns="columns" bordered :pagination="pagination" @change="changePage" :rowKey="(record, index) => index">
<a-tooltip slot="name" slot-scope="text" placement="topLeft" :title="text">