perf: http方式校验,选择sftp时,支持修改文件访问权限比如777

pull/330/head
xiaojunnuo 2025-01-20 23:29:03 +08:00
parent ae5dfc3bee
commit 15d6eaf553
9 changed files with 112 additions and 16 deletions

View File

@ -58,7 +58,7 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
}, },
required: false, required: false,
order: 100, order: 100,
helper: "PFX、jks格式证书是否加密jks必须设置密码不传则默认123456", helper: "PFX、jks格式证书是否加密\njks必须设置密码不传则默认123456",
}) })
pfxPassword!: string; pfxPassword!: string;
@ -66,12 +66,17 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
title: "PFX证书转换参数", title: "PFX证书转换参数",
value: "-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES", value: "-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES",
component: { component: {
name: "a-input", name: "a-auto-complete",
vModel: "value", vModel: "value",
options: [
{ value: "", label: "兼容 Windows Server 最新" },
{ value: "-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES", label: "兼容 Windows Server 2016" },
{ value: "-nomac -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES", label: "兼容 Windows Server 2008" },
],
}, },
required: false, required: false,
order: 100, order: 100,
helper: "兼容Server 2016如果导入证书失败请删除此参数", helper: "兼容Windows Server各个版本",
}) })
pfxArgs = "-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES"; pfxArgs = "-macalg SHA1 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES";

View File

@ -232,7 +232,7 @@ HTTP文件验证不支持泛域名需要配置网站文件上传`,
// { value: "ec_521", label: "EC 521" }, // { value: "ec_521", label: "EC 521" },
], ],
}, },
helper: "如无特殊需求,默认即可", helper: "如无特殊需求,默认即可\n选择RSA 2048 pkcs1可以获得旧版RSA证书",
required: true, required: true,
}) })
privateKeyType!: PrivateKeyType; privateKeyType!: PrivateKeyType;

View File

@ -8,6 +8,9 @@ export class HttpChallengeUploaderFactory {
} else if (type === "ssh") { } else if (type === "ssh") {
const module = await import("./impls/ssh.js"); const module = await import("./impls/ssh.js");
return module.SshHttpChallengeUploader; return module.SshHttpChallengeUploader;
} else if (type === "sftp") {
const module = await import("./impls/sftp.js");
return module.SftpHttpChallengeUploader;
} else if (type === "ftp") { } else if (type === "ftp") {
const module = await import("./impls/ftp.js"); const module = await import("./impls/ftp.js");
return module.FtpHttpChallengeUploader; return module.FtpHttpChallengeUploader;

View File

@ -0,0 +1,51 @@
import { BaseHttpChallengeUploader } from "../api.js";
import { SshAccess, SshClient } from "@certd/plugin-lib";
import path from "path";
import os from "os";
import fs from "fs";
import { SftpAccess } from "@certd/plugin-lib";
export class SftpHttpChallengeUploader extends BaseHttpChallengeUploader<SftpAccess> {
async upload(filePath: string, fileContent: Buffer) {
const tmpFilePath = path.join(os.tmpdir(), "cert", "http", filePath);
// Write file to temp path
const dir = path.dirname(tmpFilePath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(tmpFilePath, fileContent);
const access = await this.ctx.accessService.getById<SshAccess>(this.access.sshAccess);
const key = this.rootDir + filePath;
try {
const client = new SshClient(this.logger);
await client.uploadFiles({
connectConf: access,
mkdirs: true,
transports: [
{
localPath: tmpFilePath,
remotePath: key,
},
],
opts: {
mode: this.access?.fileMode ?? undefined,
},
});
} finally {
// Remove temp file
fs.unlinkSync(tmpFilePath);
}
}
async remove(filePath: string) {
const access = await this.ctx.accessService.getById<SshAccess>(this.access.sshAccess);
const client = new SshClient(this.logger);
const key = this.rootDir + filePath;
await client.removeFiles({
connectConf: access,
files: [key],
});
}
}

View File

@ -1,2 +1,3 @@
export * from "./ssh.js"; export * from "./ssh.js";
export * from "./ssh-access.js"; export * from "./ssh-access.js";
export * from "./sftp-access.js";

View File

@ -0,0 +1,34 @@
import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline";
@IsAccess({
name: "sftp",
title: "SFTP授权",
desc: "",
icon: "clarity:host-line",
input: {},
})
export class SftpAccess extends BaseAccess {
@AccessInput({
title: "SSH授权",
component: {
name: "access-selector",
type: "ssh",
vModel: "modelValue",
},
helper: "请选择一个SSH授权",
required: true,
})
sshAccess!: string;
@AccessInput({
title: "文件权限",
component: {
name: "a-input",
vModel: "value",
placeholder: "777",
},
helper: "文件上传后是否修改文件权限",
})
fileMode!: string;
}
new SftpAccess();

View File

@ -80,11 +80,11 @@ export class AsyncSsh2Client {
}); });
} }
async fastPut(options: { sftp: any; localPath: string; remotePath: string }) { async fastPut(options: { sftp: any; localPath: string; remotePath: string; opts?: { mode?: number } }) {
const { sftp, localPath, remotePath } = options; const { sftp, localPath, remotePath, opts } = options;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.logger.info(`开始上传:${localPath} => ${remotePath}`); this.logger.info(`开始上传:${localPath} => ${remotePath}`);
sftp.fastPut(localPath, remotePath, (err: Error) => { sftp.fastPut(localPath, remotePath, { ...(opts ?? {}) }, (err: Error) => {
if (err) { if (err) {
reject(err); reject(err);
this.logger.error("请确认路径是否包含文件名,路径本身不能是目录,路径不能有*?之类的特殊符号,要有写入权限"); this.logger.error("请确认路径是否包含文件名,路径本身不能是目录,路径不能有*?之类的特殊符号,要有写入权限");
@ -255,8 +255,8 @@ export class SshClient {
} }
* @param options * @param options
*/ */
async uploadFiles(options: { connectConf: SshAccess; transports: TransportItem[]; mkdirs: boolean }) { async uploadFiles(options: { connectConf: SshAccess; transports: TransportItem[]; mkdirs: boolean; opts?: { mode?: number } }) {
const { connectConf, transports, mkdirs } = options; const { connectConf, transports, mkdirs, opts } = options;
await this._call({ await this._call({
connectConf, connectConf,
callable: async (conn: AsyncSsh2Client) => { callable: async (conn: AsyncSsh2Client) => {
@ -281,7 +281,7 @@ export class SshClient {
} }
await conn.exec(mkdirCmd); await conn.exec(mkdirCmd);
} }
await conn.fastPut({ sftp, ...transport }); await conn.fastPut({ sftp, ...transport, opts });
} }
this.logger.info("文件全部上传成功"); this.logger.info("文件全部上传成功");
}, },

View File

@ -30,7 +30,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { Ref, ref, watch } from "vue"; import { Ref, ref, watch, nextTick } from "vue";
import { HttpRecord } from "/@/components/plugins/cert/domains-verify-plan-editor/type"; import { HttpRecord } from "/@/components/plugins/cert/domains-verify-plan-editor/type";
import { dict } from "@fast-crud/fast-crud"; import { dict } from "@fast-crud/fast-crud";
@ -62,18 +62,20 @@ watch(
} }
); );
function onRecordChange() { async function onRecordChange() {
await nextTick();
emit("update:modelValue", records.value); emit("update:modelValue", records.value);
emit("change", records.value); emit("change", records.value);
} }
const uploaderTypeDict = dict({ const uploaderTypeDict = dict({
data: [ data: [
{ label: "SFTP/SSH", value: "ssh" }, { label: "SFTP", value: "sftp" },
{ label: "FTP", value: "ftp" }, { label: "FTP", value: "ftp" },
{ label: "阿里云OSS", value: "alioss" }, { label: "阿里云OSS", value: "alioss" },
{ label: "腾讯云COS", value: "tencentcos" }, { label: "腾讯云COS", value: "tencentcos" },
{ label: "七牛OSS", value: "qiniuoss" } { label: "七牛OSS", value: "qiniuoss" },
{ label: "SSH(已废弃请选择SFTP方式)", value: "ssh", disabled: true }
] ]
}); });
</script> </script>

View File

@ -5,10 +5,10 @@ import { SshAccess, SshClient } from '@certd/plugin-lib';
@IsTaskPlugin({ @IsTaskPlugin({
name: 'uploadCertToHost', name: 'uploadCertToHost',
title: '主机-部署证书到主机', title: '主机-部署证书到SSH主机',
icon: 'line-md:uploading-loop', icon: 'line-md:uploading-loop',
group: pluginGroups.host.key, group: pluginGroups.host.key,
desc: '上传证书到主机,然后执行部署脚本命令', desc: 'SFTP上传证书到主机,然后SSH执行部署脚本命令',
default: { default: {
strategy: { strategy: {
runStrategy: RunStrategy.SkipWhenSucceed, runStrategy: RunStrategy.SkipWhenSucceed,