From 3bad0b2685fb211e71c131c3cdb7aa3e1389250e Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Fri, 6 Sep 2024 00:13:21 +0800 Subject: [PATCH] chore: 1 --- packages/core/pipeline/src/core/executor.ts | 8 +- .../core/pipeline/src/core/run-history.ts | 2 - packages/core/pipeline/src/dt/pipeline.ts | 2 +- packages/core/pipeline/src/utils/util.sp.ts | 4 +- .../src/plugin/cert-convert/index.ts | 160 ------------------ .../src/plugin/cert-plugin/acme.ts | 2 + .../src/plugin/cert-plugin/base.ts | 42 ++++- .../src/plugin/cert-plugin/cert-reader.ts | 35 ++-- .../src/plugin/cert-plugin/convert.ts | 91 ++++++++++ .../plugins/plugin-cert/src/plugin/index.ts | 1 - .../plugin/deploy-to-ack-ingress/index.ts | 2 +- .../plugin-host/plugin/copy-to-local/index.ts | 97 +++++++---- .../plugin/upload-to-host/index.ts | 86 ++++++++-- 13 files changed, 292 insertions(+), 240 deletions(-) delete mode 100644 packages/plugins/plugin-cert/src/plugin/cert-convert/index.ts create mode 100644 packages/plugins/plugin-cert/src/plugin/cert-plugin/convert.ts diff --git a/packages/core/pipeline/src/core/executor.ts b/packages/core/pipeline/src/core/executor.ts index 98eae03be..fa93c86e0 100644 --- a/packages/core/pipeline/src/core/executor.ts +++ b/packages/core/pipeline/src/core/executor.ts @@ -96,14 +96,13 @@ export class Executor { runnable.runnableType = runnableType; this.runtime.start(runnable); await this.onChanged(this.runtime); - + const lastNode = this.lastStatusMap.get(runnable.id); + const lastResult = lastNode?.status?.status; if (runnable.strategy?.runStrategy === RunStrategy.SkipWhenSucceed) { //如果是成功后跳过策略 - const lastNode = this.lastStatusMap.get(runnable.id); - const lastResult = lastNode?.status?.status; - const lastInput = JSON.stringify(lastNode?.status?.input); let inputChanged = false; if (runnableType === "step") { + const lastInput = JSON.stringify((lastNode as Step)?.input); const step = runnable as Step; const input = JSON.stringify(step.input); if (input != null && lastInput !== input) { @@ -271,7 +270,6 @@ export class Executor { // this.runtime.context[stepOutputKey] = instance[key]; }); step.status!.files = instance.getFiles(); - //更新pipeline vars if (Object.keys(instance._result.pipelineVars).length > 0) { // 判断 pipelineVars 有值时更新 diff --git a/packages/core/pipeline/src/core/run-history.ts b/packages/core/pipeline/src/core/run-history.ts index 64341ed61..4e4e6833b 100644 --- a/packages/core/pipeline/src/core/run-history.ts +++ b/packages/core/pipeline/src/core/run-history.ts @@ -41,10 +41,8 @@ export class RunHistory { this._loggers[runnable.id] = buildLogger((text) => { this.logs[runnable.id].push(text); }); - const input = (runnable as Step).input; const status: HistoryResult = { output: {}, - input: _.cloneDeep(input), status: ResultType.start, startTime: now, result: ResultType.start, diff --git a/packages/core/pipeline/src/dt/pipeline.ts b/packages/core/pipeline/src/dt/pipeline.ts index 3def34839..7cfa78854 100644 --- a/packages/core/pipeline/src/dt/pipeline.ts +++ b/packages/core/pipeline/src/dt/pipeline.ts @@ -118,7 +118,7 @@ export type HistoryResultGroup = { }; }; export type HistoryResult = { - input: any; + // input: any; output: any; files?: FileItem[]; /** diff --git a/packages/core/pipeline/src/utils/util.sp.ts b/packages/core/pipeline/src/utils/util.sp.ts index c7710e15b..09cef2307 100644 --- a/packages/core/pipeline/src/utils/util.sp.ts +++ b/packages/core/pipeline/src/utils/util.sp.ts @@ -66,8 +66,8 @@ async function spawn(opts: SpawnOption): Promise { cmd = item; } } - }else{ - cmd = opts.cmd + } else { + cmd = opts.cmd; } log.info(`执行命令: ${cmd}`); let stdout = ""; diff --git a/packages/plugins/plugin-cert/src/plugin/cert-convert/index.ts b/packages/plugins/plugin-cert/src/plugin/cert-convert/index.ts deleted file mode 100644 index f48ff3a68..000000000 --- a/packages/plugins/plugin-cert/src/plugin/cert-convert/index.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, sp, TaskInput, TaskOutput } from "@certd/pipeline"; -import type { CertInfo } from "../cert-plugin/acme.js"; -import { CertReader, CertReaderHandleContext } from "../cert-plugin/cert-reader.js"; -import path from "path"; -import os from "os"; -import fs from "fs"; - -export { CertReader }; -export type { CertInfo }; - -@IsTaskPlugin({ - name: "CertConvert", - title: "证书转换器", - group: pluginGroups.cert.key, - desc: "转换为pfx、der等证书格式", - default: { - input: { - renewDays: 20, - forceUpdate: false, - }, - strategy: { - runStrategy: RunStrategy.AlwaysRun, - }, - }, -}) -export class CertConvertPlugin extends AbstractTaskPlugin { - @TaskInput({ - title: "域名证书", - helper: "请选择前置任务输出的域名证书", - component: { - name: "pi-output-selector", - from: "CertApply", - }, - required: true, - }) - cert!: CertInfo; - - @TaskInput({ - title: "PFX证书密码", - helper: "不填则没有密码", - component: { - name: "a-input-password", - vModel: "value", - }, - required: false, - }) - pfxPassword!: string; - - @TaskInput({ - title: "输出PFX", - value:true, - component: { - name: "a-switch", - vModel: "checked", - }, - required: true, - }) - pfxEnabled: boolean = true; - - - @TaskInput({ - title: "输出DER", - value:true, - component: { - name: "a-switch", - vModel: "checked", - }, - required: true, - }) - derEnabled: boolean = true; - - - - @TaskOutput({ - title: "pfx格式证书", - type: "PfxCert", - }) - pfxCert?: string; - - @TaskOutput({ - title: "der格式证书", - type: "DerCert", - }) - derCert?: string; - - async onInit() {} - - async execute(): Promise { - const certReader = new CertReader(this.cert); - - const handle = async (opts: CertReaderHandleContext) => { - if(this.pfxEnabled){ - // 调用openssl 转pfx - await this.convertPfx(opts); - }else{ - this.logger.info("pfx证书已禁用"); - } - - if(this.pfxEnabled){ - // 转der - await this.convertDer(opts); - }else{ - this.logger.info("der证书已禁用"); - } - }; - - await certReader.readCertFile({ logger: this.logger, handle }); - } - - async exec(cmd: string) { - await sp.spawn({ - cmd: cmd, - logger: this.logger, - }); - } - - private async convertPfx(opts: CertReaderHandleContext) { - const { reader, tmpCrtPath, tmpKeyPath } = opts; - - const pfxPath = path.join(os.tmpdir(), "/certd/tmp/", Math.floor(Math.random() * 1000000) + "", "cert.pfx"); - - const dir = path.dirname(pfxPath) - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); - } - - let passwordArg = "-passout pass:"; - if (this.pfxPassword) { - passwordArg = `-password pass:${this.pfxPassword}`; - } - await this.exec(`openssl pkcs12 -export -out ${pfxPath} -inkey ${tmpKeyPath} -in ${tmpCrtPath} ${passwordArg}`); - this.pfxCert = pfxPath; - - const applyTime = new Date().getTime(); - const filename = reader.buildCertFileName("pfx", applyTime); - const fileBuffer = fs.readFileSync(pfxPath); - this.saveFile(filename, fileBuffer); - } - - private async convertDer(opts: CertReaderHandleContext) { - const { reader, tmpCrtPath } = opts; - const derPath = path.join(os.tmpdir(), "/certd/tmp/", Math.floor(Math.random() * 1000000) + "", `cert.der`); - - const dir = path.dirname(derPath) - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); - } - - - await this.exec(`openssl x509 -outform der -in ${tmpCrtPath} -out ${derPath}`); - this.derCert = derPath; - - const applyTime = new Date().getTime(); - const filename = reader.buildCertFileName("der", applyTime); - const fileBuffer = fs.readFileSync(derPath); - this.saveFile(filename, fileBuffer); - } -} - -new CertConvertPlugin(); diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts index c9aa6e482..753d85c21 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts @@ -12,6 +12,8 @@ export type CertInfo = { crt: string; key: string; csr: string; + pfx?: string; + der?: string; }; export type SSLProvider = "letsencrypt" | "google" | "zerossl"; export type PrivateKeyType = "rsa_1024" | "rsa_2048" | "rsa_3072" | "rsa_4096" | "ec_256" | "ec_384" | "ec_521"; diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts index d0da67897..9e02f0618 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts @@ -3,6 +3,8 @@ import dayjs from "dayjs"; import type { CertInfo } from "./acme.js"; import { CertReader } from "./cert-reader.js"; import JSZip from "jszip"; +import { CertConverter } from "./convert.js"; +import fs from "fs"; export { CertReader }; export type { CertInfo }; @@ -42,6 +44,18 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin { }) email!: string; + @TaskInput({ + title: "PFX密码", + component: { + name: "a-input-password", + vModel: "value", + }, + required: false, + order: 100, + helper: "PFX格式证书是否需要加密", + }) + pfxPassword!: string; + @TaskInput({ title: "更新天数", value: 20, @@ -129,22 +143,36 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin { this._result.pipelineVars.certExpiresTime = dayjs(certReader.detail.notAfter).valueOf(); + if (cert.pfx == null || cert.der == null) { + const converter = new CertConverter({ logger: this.logger }); + const res = await converter.convert({ + cert, + pfxPassword: this.pfxPassword, + }); + const pfxBuffer = fs.readFileSync(res.pfxPath); + cert.pfx = pfxBuffer.toString("base64"); + + const derBuffer = fs.readFileSync(res.derPath); + cert.der = derBuffer.toString("base64"); + + this.logger.info("转换证书格式成功"); + isNew = true; + } + if (isNew) { - const applyTime = dayjs(certReader.detail.notBefore).format("YYYYMMDD_HHmmss"); - await this.zipCert(cert, applyTime); + const zipFileName = certReader.buildCertFileName("zip", certReader.detail.notBefore); + await this.zipCert(cert, zipFileName); } else { this.extendsFiles(); } - // thi - // s.logger.info(JSON.stringify(certReader.detail)); } - async zipCert(cert: CertInfo, applyTime: string) { + async zipCert(cert: CertInfo, filename: string) { const zip = new JSZip(); zip.file("cert.crt", cert.crt); zip.file("cert.key", cert.key); - const domain_name = this.domains[0].replace(".", "_").replace("*", "_"); - const filename = `cert_${domain_name}_${applyTime}.zip`; + zip.file("cert.pfx", Buffer.from(cert.pfx, "base64")); + zip.file("cert.der", Buffer.from(cert.der, "base64")); const content = await zip.generateAsync({ type: "nodebuffer" }); this.saveFile(filename, content); this.logger.info(`已保存文件:${filename}`); diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/cert-reader.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/cert-reader.ts index 312495a08..af7478f8b 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/cert-reader.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/cert-reader.ts @@ -6,10 +6,11 @@ import { crypto } from "@certd/acme-client"; import { ILogger } from "@certd/pipeline"; import dayjs from "dayjs"; -export type CertReaderHandleContext = { reader: CertReader; tmpCrtPath: string; tmpKeyPath: string }; +export type CertReaderHandleContext = { reader: CertReader; tmpCrtPath: string; tmpKeyPath: string; tmpPfxPath?: string; tmpDerPath?: string }; export type CertReaderHandle = (ctx: CertReaderHandleContext) => Promise; export type HandleOpts = { logger: ILogger; handle: CertReaderHandle }; -export class CertReader implements CertInfo { +export class CertReader { + cert: CertInfo; crt: string; key: string; csr: string; @@ -17,30 +18,31 @@ export class CertReader implements CertInfo { detail: any; expires: number; constructor(certInfo: CertInfo) { + this.cert = certInfo; this.crt = certInfo.crt; this.key = certInfo.key; this.csr = certInfo.csr; - const { detail, expires } = this.getCrtDetail(this.crt); + const { detail, expires } = this.getCrtDetail(this.cert.crt); this.detail = detail; this.expires = expires.getTime(); } toCertInfo(): CertInfo { - return { - crt: this.crt, - key: this.key, - csr: this.csr, - }; + return this.cert; } - getCrtDetail(crt: string = this.crt) { + getCrtDetail(crt: string = this.cert.crt) { const detail = crypto.readCertificateInfo(crt.toString()); const expires = detail.notAfter; return { detail, expires }; } - saveToFile(type: "crt" | "key", filepath?: string) { + saveToFile(type: "crt" | "key" | "pfx" | "der", filepath?: string) { + if (!this.cert[type]) { + return; + } + if (filepath == null) { //写入临时目录 filepath = path.join(os.tmpdir(), "/certd/tmp/", Math.floor(Math.random() * 1000000) + "", `cert.${type}`); @@ -50,8 +52,11 @@ export class CertReader implements CertInfo { if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } - - fs.writeFileSync(filepath, this[type]); + if (type === "crt" || type === "key") { + fs.writeFileSync(filepath, this.cert[type]); + } else { + fs.writeFileSync(filepath, Buffer.from(this.cert[type], "base64")); + } return filepath; } @@ -60,18 +65,24 @@ export class CertReader implements CertInfo { logger.info("将证书写入本地缓存文件"); const tmpCrtPath = this.saveToFile("crt"); const tmpKeyPath = this.saveToFile("key"); + const tmpPfxPath = this.saveToFile("pfx"); + const tmpDerPath = this.saveToFile("der"); logger.info("本地文件写入成功"); try { await opts.handle({ reader: this, tmpCrtPath: tmpCrtPath, tmpKeyPath: tmpKeyPath, + tmpPfxPath: tmpPfxPath, + tmpDerPath: tmpDerPath, }); } finally { //删除临时文件 logger.info("删除临时文件"); fs.unlinkSync(tmpCrtPath); fs.unlinkSync(tmpKeyPath); + fs.unlinkSync(tmpPfxPath); + fs.unlinkSync(tmpDerPath); } } diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/convert.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/convert.ts new file mode 100644 index 000000000..a498e70dd --- /dev/null +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/convert.ts @@ -0,0 +1,91 @@ +import { ILogger, sp } from "@certd/pipeline"; +import type { CertInfo } from "../cert-plugin/acme.js"; +import { CertReader, CertReaderHandleContext } from "../cert-plugin/cert-reader.js"; +import path from "path"; +import os from "os"; +import fs from "fs"; + +export { CertReader }; +export type { CertInfo }; + +export class CertConverter { + logger: ILogger; + + constructor(opts: { logger: ILogger }) { + this.logger = opts.logger; + } + async convert(opts: { cert: CertInfo; pfxPassword: string }): Promise<{ + pfxPath: string; + derPath: string; + }> { + const certReader = new CertReader(opts.cert); + let pfxPath: string; + let derPath: string; + const handle = async (opts: CertReaderHandleContext) => { + // 调用openssl 转pfx + pfxPath = await this.convertPfx(opts); + + // 转der + derPath = await this.convertDer(opts); + }; + + await certReader.readCertFile({ logger: this.logger, handle }); + + return { + pfxPath, + derPath, + }; + } + + async exec(cmd: string) { + await sp.spawn({ + cmd: cmd, + logger: this.logger, + }); + } + + private async convertPfx(opts: CertReaderHandleContext, pfxPassword?: string) { + const { tmpCrtPath, tmpKeyPath } = opts; + + const pfxPath = path.join(os.tmpdir(), "/certd/tmp/", Math.floor(Math.random() * 1000000) + "", "cert.pfx"); + + const dir = path.dirname(pfxPath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + + let passwordArg = "-passout pass:"; + if (pfxPassword) { + passwordArg = `-password pass:${pfxPassword}`; + } + await this.exec(`openssl pkcs12 -export -out ${pfxPath} -inkey ${tmpKeyPath} -in ${tmpCrtPath} ${passwordArg}`); + return pfxPath; + // const fileBuffer = fs.readFileSync(pfxPath); + // this.pfxCert = fileBuffer.toString("base64"); + // + // const applyTime = new Date().getTime(); + // const filename = reader.buildCertFileName("pfx", applyTime); + // this.saveFile(filename, fileBuffer); + } + + private async convertDer(opts: CertReaderHandleContext) { + const { tmpCrtPath } = opts; + const derPath = path.join(os.tmpdir(), "/certd/tmp/", Math.floor(Math.random() * 1000000) + "", `cert.der`); + + const dir = path.dirname(derPath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + + await this.exec(`openssl x509 -outform der -in ${tmpCrtPath} -out ${derPath}`); + + return derPath; + + // const fileBuffer = fs.readFileSync(derPath); + // this.derCert = fileBuffer.toString("base64"); + // + // const applyTime = new Date().getTime(); + // const filename = reader.buildCertFileName("der", applyTime); + // this.saveFile(filename, fileBuffer); + } +} diff --git a/packages/plugins/plugin-cert/src/plugin/index.ts b/packages/plugins/plugin-cert/src/plugin/index.ts index f6de18c9d..aaafb3500 100644 --- a/packages/plugins/plugin-cert/src/plugin/index.ts +++ b/packages/plugins/plugin-cert/src/plugin/index.ts @@ -1,3 +1,2 @@ export * from "./cert-plugin/index.js"; export * from "./cert-plugin/lego/index.js"; -export * from "./cert-convert/index.js"; diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-ack-ingress/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-ack-ingress/index.ts index e4b92b7c0..d735c17fc 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-ack-ingress/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-ack-ingress/index.ts @@ -51,7 +51,7 @@ export class DeployCertToAliyunAckIngressPlugin extends AbstractTaskPlugin { }, required: true, }) - namespace!: string; + namespace: string = 'default'; @TaskInput({ title: 'ingress名称', value: '', diff --git a/packages/ui/certd-server/src/plugins/plugin-host/plugin/copy-to-local/index.ts b/packages/ui/certd-server/src/plugins/plugin-host/plugin/copy-to-local/index.ts index 077afbcec..5cd0b23e8 100644 --- a/packages/ui/certd-server/src/plugins/plugin-host/plugin/copy-to-local/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-host/plugin/copy-to-local/index.ts @@ -17,28 +17,46 @@ import path from 'path'; export class CopyCertToLocalPlugin extends AbstractTaskPlugin { @TaskInput({ title: '证书保存路径', - helper: '需要有写入权限,路径要包含证书文件名,文件名不能用*?!等特殊符号\n推荐使用相对路径,将写入与数据库同级目录,无需映射,例如:./tmp/cert.pem', + helper: '需要有写入权限,路径要包含文件名,文件名不能用*?!等特殊符号\n推荐使用相对路径,将写入与数据库同级目录,无需映射,例如:./tmp/cert.pem', component: { placeholder: './tmp/cert.pem', }, - required: true, }) crtPath!: string; @TaskInput({ title: '私钥保存路径', - helper: '需要有写入权限,路径要包含私钥文件名,文件名不能用*?!等特殊符号\n推荐使用相对路径,将写入与数据库同级目录,无需映射,例如:./tmp/cert.key', + helper: '需要有写入权限,路径要包含文件名,文件名不能用*?!等特殊符号\n推荐使用相对路径,将写入与数据库同级目录,无需映射,例如:./tmp/cert.key', component: { placeholder: './tmp/cert.key', }, - required: true, }) keyPath!: string; + + @TaskInput({ + title: 'PFX证书保存路径', + helper: '需要有写入权限,路径要包含文件名,文件名不能用*?!等特殊符号\n推荐使用相对路径,将写入与数据库同级目录,无需映射,例如:./tmp/cert.pfx', + component: { + placeholder: './tmp/cert.pfx', + }, + }) + pfxPath!: string; + + @TaskInput({ + title: 'DER证书保存路径', + helper: + '需要有写入权限,路径要包含文件名,文件名不能用*?!等特殊符号\n推荐使用相对路径,将写入与数据库同级目录,无需映射,例如:./tmp/cert.der\n.der和.cer是相同的东西,改个后缀名即可', + component: { + placeholder: './tmp/cert.der 或 ./tmp/cert.cer', + }, + }) + derPath!: string; + @TaskInput({ title: '域名证书', helper: '请选择前置任务输出的域名证书', component: { name: 'pi-output-selector', - from: ['CertApply','CertConvert'], + from: 'CertApply', }, required: true, }) @@ -46,16 +64,28 @@ export class CopyCertToLocalPlugin extends AbstractTaskPlugin { @TaskOutput({ title: '证书保存路径', - type:"HostCrtPath" + type: 'HostCrtPath', }) hostCrtPath!: string; @TaskOutput({ title: '私钥保存路径', - type:"HostKeyPath" + type: 'HostKeyPath', }) hostKeyPath!: string; + @TaskOutput({ + title: 'PFX保存路径', + type: 'HostPfxPath', + }) + hostPfxPath!: string; + + @TaskOutput({ + title: 'DER保存路径', + type: 'HostDerPath', + }) + hostDerPath!: string; + async onInstance() {} copyFile(srcFile: string, destFile: string) { @@ -67,37 +97,38 @@ export class CopyCertToLocalPlugin extends AbstractTaskPlugin { fs.copyFileSync(srcFile, destFile); } async execute(): Promise { - let { crtPath, keyPath } = this; + let { crtPath, keyPath, pfxPath, derPath } = this; const certReader = new CertReader(this.cert); - this.logger.info('将证书写入本地缓存文件'); - const saveCrtPath = certReader.saveToFile('crt'); - const saveKeyPath = certReader.saveToFile('key'); - this.logger.info('本地文件写入成功'); - try { - this.logger.info('复制到目标路径'); - crtPath = crtPath.startsWith('/') ? crtPath : path.join(Constants.dataDir, crtPath); - keyPath = keyPath.startsWith('/') ? keyPath : path.join(Constants.dataDir, keyPath); - // crtPath = path.resolve(crtPath); - // keyPath = path.resolve(keyPath); - this.copyFile(saveCrtPath, crtPath); - this.copyFile(saveKeyPath, keyPath); - this.logger.info('证书复制成功:crtPath=', crtPath, ',keyPath=', keyPath); + const handle = async ({ reader, tmpCrtPath, tmpKeyPath, tmpDerPath, tmpPfxPath }) => { + this.logger.info('复制到目标路径'); + if (crtPath) { + crtPath = crtPath.startsWith('/') ? crtPath : path.join(Constants.dataDir, crtPath); + this.copyFile(tmpCrtPath, crtPath); + this.hostCrtPath = crtPath; + } + if (keyPath) { + keyPath = keyPath.startsWith('/') ? keyPath : path.join(Constants.dataDir, keyPath); + this.copyFile(tmpKeyPath, keyPath); + this.hostKeyPath = keyPath; + } + if (pfxPath) { + pfxPath = pfxPath.startsWith('/') ? pfxPath : path.join(Constants.dataDir, pfxPath); + this.copyFile(tmpPfxPath, pfxPath); + this.hostPfxPath = pfxPath; + } + if (derPath) { + derPath = derPath.startsWith('/') ? derPath : path.join(Constants.dataDir, derPath); + this.copyFile(tmpDerPath, derPath); + this.hostDerPath = derPath; + } this.logger.info('请注意,如果使用的是相对路径,那么文件就在你的数据库同级目录下,默认是/data/certd/下面'); this.logger.info('请注意,如果使用的是绝对路径,文件在容器内的目录下,你需要给容器做目录映射才能复制到宿主机'); - } catch (e) { - this.logger.error(`复制失败:${e.message}`); - throw e; - } finally { - //删除临时文件 - this.logger.info('删除临时文件'); - fs.unlinkSync(saveCrtPath); - fs.unlinkSync(saveKeyPath); - } + }; + + await certReader.readCertFile({ logger: this.logger, handle }); + this.logger.info('执行完成'); - //输出 - this.hostCrtPath = crtPath; - this.hostKeyPath = keyPath; } } diff --git a/packages/ui/certd-server/src/plugins/plugin-host/plugin/upload-to-host/index.ts b/packages/ui/certd-server/src/plugins/plugin-host/plugin/upload-to-host/index.ts index c42bf5eeb..e1ef6235b 100644 --- a/packages/ui/certd-server/src/plugins/plugin-host/plugin/upload-to-host/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-host/plugin/upload-to-host/index.ts @@ -17,8 +17,8 @@ import { SshAccess } from '../../access/index.js'; }) export class UploadCertToHostPlugin extends AbstractTaskPlugin { @TaskInput({ - title: '证书保存路径', - helper: '需要有写入权限,路径要包含证书文件名,文件名不能用*?!等特殊符号', + title: 'PEM证书保存路径', + helper: '需要有写入权限,路径要包含证书文件名,文件名不能用*?!等特殊符号,例如:/tmp/cert.pem', component: { placeholder: '/root/deploy/nginx/cert.pem', }, @@ -26,12 +26,31 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin { crtPath!: string; @TaskInput({ title: '私钥保存路径', - helper: '需要有写入权限,路径要包含私钥文件名,文件名不能用*?!等特殊符号', + helper: '需要有写入权限,路径要包含私钥文件名,文件名不能用*?!等特殊符号,例如:/tmp/cert.key', component: { placeholder: '/root/deploy/nginx/cert.key', }, }) keyPath!: string; + + @TaskInput({ + title: 'PFX证书保存路径', + helper: '需要有写入权限,路径要包含私钥文件名,文件名不能用*?!等特殊符号,例如:/tmp/cert.pfx', + component: { + placeholder: '/root/deploy/nginx/cert.pfx', + }, + }) + pfxPath!: string; + + @TaskInput({ + title: 'DER证书保存路径', + helper: '需要有写入权限,路径要包含私钥文件名,文件名不能用*?!等特殊符号,例如:/tmp/cert.der', + component: { + placeholder: '/root/deploy/nginx/cert.der', + }, + }) + derPath!: string; + @TaskInput({ title: '域名证书', helper: '请选择前置任务输出的域名证书', @@ -87,9 +106,23 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin { }) hostKeyPath!: string; + @TaskOutput({ + title: 'PFX保存路径', + }) + hostPfxPath!: string; + + @TaskOutput({ + title: 'DER保存路径', + }) + hostDerPath!: string; + async onInstance() {} copyFile(srcFile: string, destFile: string) { + if (!srcFile || !destFile) { + this.logger.warn(`srcFile:${srcFile} 或 destFile:${destFile} 为空,不复制`); + return; + } const dir = destFile.substring(0, destFile.lastIndexOf('/')); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); @@ -101,12 +134,15 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin { const certReader = new CertReader(cert); const handle = async (opts: CertReaderHandleContext) => { - const { tmpCrtPath, tmpKeyPath } = opts; + const { tmpCrtPath, tmpKeyPath, tmpDerPath, tmpPfxPath } = opts; if (this.copyToThisHost) { this.logger.info('复制到目标路径'); this.copyFile(tmpCrtPath, crtPath); this.copyFile(tmpKeyPath, keyPath); - this.logger.info('证书复制成功:crtPath=', crtPath, ',keyPath=', keyPath); + this.copyFile(tmpPfxPath, this.pfxPath); + this.copyFile(tmpDerPath, this.derPath); + + this.logger.info(`证书复制成功:crtPath=${crtPath},keyPath=${keyPath},pfxPath=${this.pfxPath},derPath=${this.derPath}`); } else { if (!accessId) { throw new Error('主机登录授权配置不能为空'); @@ -114,25 +150,43 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin { this.logger.info('准备上传文件到服务器'); const connectConf: SshAccess = await this.accessService.getById(accessId); const sshClient = new SshClient(this.logger); + const transports: any = []; + if (crtPath) { + transports.push({ + localPath: tmpCrtPath, + remotePath: crtPath, + }); + } + if (keyPath) { + transports.push({ + localPath: tmpKeyPath, + remotePath: keyPath, + }); + } + if (this.pfxPath) { + transports.push({ + localPath: tmpPfxPath, + remotePath: this.pfxPath, + }); + } + if (this.derPath) { + transports.push({ + localPath: tmpDerPath, + remotePath: this.derPath, + }); + } await sshClient.uploadFiles({ connectConf, - transports: [ - { - localPath: tmpCrtPath, - remotePath: crtPath, - }, - { - localPath: tmpKeyPath, - remotePath: keyPath, - }, - ], + transports, mkdirs: this.mkdirs, }); - this.logger.info('证书上传成功:crtPath=', crtPath, ',keyPath=', keyPath); + this.logger.info(`证书上传成功:crtPath=${crtPath},keyPath=${keyPath},pfxPath=${this.pfxPath},derPath=${this.derPath}`); //输出 this.hostCrtPath = crtPath; this.hostKeyPath = keyPath; + this.hostPfxPath = this.pfxPath; + this.hostDerPath = this.derPath; } }; await certReader.readCertFile({