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 8ff096673..53ef4b13b 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts @@ -14,7 +14,7 @@ export type CertInfo = { csr: string; }; export type SSLProvider = "letsencrypt" | "google" | "zerossl"; -export type PrivateKeyType = "rsa" | "ec"; +export type PrivateKeyType = "rsa_1024" | "rsa_2048" | "rsa_3072" | "rsa_4096" | "ec_256" | "ec_384" | "ec_521"; type AcmeServiceOptions = { userContext: IContext; logger: Logger; @@ -226,12 +226,16 @@ export class AcmeService { /* Create CSR */ const { commonName, altNames } = this.buildCommonNameByDomains(domains); let privateKey = null; - if (options.privateKeyType == "ec") { - privateKey = await acme.crypto.createPrivateEcdsaKey(); + const privateKeyArr = options.privateKeyType.split("_"); + const type = privateKeyArr[0]; + const size = parseInt(privateKeyArr[1]); + if (type == "ec") { + const name: any = "P-" + size; + privateKey = await acme.crypto.createPrivateEcdsaKey(name); } else { - privateKey = await acme.crypto.createPrivateRsaKey(); + privateKey = await acme.crypto.createPrivateRsaKey(size); } - const [key, csr] = await acme.forge.createCsr( + const [key, csr] = await acme.crypto.createCsr( { commonName, ...csrInfo, 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 984cf437c..a443c0d23 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts @@ -133,10 +133,10 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin { const cert: CertInfo = certReader.toCertInfo(); this.cert = cert; - this._result.pipelineVars.certExpiresTime = dayjs(certReader.detail.validity.notAfter).valueOf(); + this._result.pipelineVars.certExpiresTime = dayjs(certReader.detail.notAfter).valueOf(); if (isNew) { - const applyTime = dayjs(certReader.detail.validity.notBefore).format("YYYYMMDD_HHmmss"); + const applyTime = dayjs(certReader.detail.notBefore).format("YYYYMMDD_HHmmss"); await this.zipCert(cert, applyTime); } else { this.extendsFiles(); 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 ec76f9139..babe3af48 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 @@ -1,8 +1,8 @@ import { CertInfo } from "./acme.js"; import fs from "fs"; import os from "os"; -import forge from "node-forge"; import path from "path"; +import { crypto } from "@certd/acme-client"; export class CertReader implements CertInfo { crt: string; key: string; @@ -29,9 +29,8 @@ export class CertReader implements CertInfo { } getCrtDetail(crt: string) { - const pki = forge.pki; - const detail = pki.certificateFromPem(crt.toString()); - const expires = detail.validity.notAfter; + const detail = crypto.readCertificateInfo(crt.toString()); + const expires = detail.notAfter; return { detail, expires }; } diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts index 7d44f35cc..41ce70caf 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts @@ -44,13 +44,18 @@ export class CertApplyPlugin extends CertApplyBasePlugin { @TaskInput({ title: "证书私钥类型", - value: "rsa", + value: "rsa_2048", component: { name: "a-select", vModel: "value", options: [ - { value: "rsa", label: "RSA" }, - { value: "ec", label: "EC" }, + { value: "rsa_1024", label: "RSA 1024" }, + { value: "rsa_2048", label: "RSA 2048" }, + { value: "rsa_3072", label: "RSA 3072" }, + { value: "rsa_4096", label: "RSA 4096" }, + { value: "ec_256", label: "EC 256" }, + { value: "ec_384", label: "EC 384" }, + { value: "ec_521", label: "EC 521" }, ], }, required: true, diff --git a/packages/ui/certd-client/src/views/certd/pipeline/crud.tsx b/packages/ui/certd-client/src/views/certd/pipeline/crud.tsx index c7030c274..273934878 100644 --- a/packages/ui/certd-client/src/views/certd/pipeline/crud.tsx +++ b/packages/ui/certd-client/src/views/certd/pipeline/crud.tsx @@ -342,7 +342,7 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp title: "历史记录保持数", type: "number", form: { - value: 10, + value: 20, helper: "历史记录保持条数,多余的会被删除" }, column: { diff --git a/packages/ui/certd-server/src/basic/constants.ts b/packages/ui/certd-server/src/basic/constants.ts index ca329683e..b29336669 100644 --- a/packages/ui/certd-server/src/basic/constants.ts +++ b/packages/ui/certd-server/src/basic/constants.ts @@ -1,4 +1,5 @@ export const Constants = { + dataDir: './data', role: { defaultUser: 3, }, diff --git a/packages/ui/certd-server/src/modules/pipeline/service/history-service.ts b/packages/ui/certd-server/src/modules/pipeline/service/history-service.ts index d32e6a06a..5e38c095d 100644 --- a/packages/ui/certd-server/src/modules/pipeline/service/history-service.ts +++ b/packages/ui/certd-server/src/modules/pipeline/service/history-service.ts @@ -63,7 +63,7 @@ export class HistoryService extends BaseService { return id; } - private async clear(pipelineId: number, keepCount = 10) { + private async clear(pipelineId: number, keepCount = 20) { const count = await this.repository.count({ where: { pipelineId, 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 new file mode 100644 index 000000000..5358884e3 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-host/plugin/copy-to-local/index.ts @@ -0,0 +1,99 @@ +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline'; +import { CertInfo, CertReader } from '@certd/plugin-cert'; +import * as fs from 'fs'; +import { Constants } from '../../../../basic/constants.js'; +import path from 'path'; + +@IsTaskPlugin({ + name: 'CopyToLocal', + title: '复制到本机', + group: pluginGroups.host.key, + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, +}) +export class CopyCertToLocalPlugin extends AbstractTaskPlugin { + @TaskInput({ + title: '证书保存路径', + helper: '需要有写入权限,路径要包含证书文件名,文件名不能用*?!等特殊符号\n推荐使用相对路径,将写入与数据库同级目录,无需映射,例如:./tmp/cert.pem', + component: { + placeholder: './tmp/cert.pem', + }, + }) + crtPath!: string; + @TaskInput({ + title: '私钥保存路径', + helper: '需要有写入权限,路径要包含私钥文件名,文件名不能用*?!等特殊符号\n推荐使用相对路径,将写入与数据库同级目录,无需映射,例如:./tmp/cert.key', + component: { + placeholder: './tmp/cert.key', + }, + }) + keyPath!: string; + @TaskInput({ + title: '域名证书', + helper: '请选择前置任务输出的域名证书', + component: { + name: 'pi-output-selector', + }, + required: true, + }) + cert!: CertInfo; + + @TaskOutput({ + title: '证书保存路径', + }) + hostCrtPath!: string; + + @TaskOutput({ + title: '私钥保存路径', + }) + hostKeyPath!: string; + + async onInstance() {} + + copyFile(srcFile: string, destFile: string) { + this.logger.info(`复制文件:${srcFile} => ${destFile}`); + const dir = path.dirname(destFile); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + fs.copyFileSync(srcFile, destFile); + } + async execute(): Promise { + let { crtPath, keyPath } = 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); + 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); + } + this.logger.info('执行完成'); + //输出 + this.hostCrtPath = crtPath; + this.hostKeyPath = keyPath; + } +} + +new CopyCertToLocalPlugin();