mirror of https://github.com/certd/certd
perf: http方式校验,选择sftp时,支持修改文件访问权限比如777
parent
ae5dfc3bee
commit
15d6eaf553
|
@ -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";
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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";
|
||||||
|
|
|
@ -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();
|
|
@ -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("文件全部上传成功");
|
||||||
},
|
},
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue