From 51b6fed468eaa6f28ce4497ce303ace1a52abb96 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Mon, 28 Oct 2024 15:31:45 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E9=BB=98=E8=AE=A4=E8=AF=81=E4=B9=A6?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=97=B6=E9=97=B4=E8=AE=BE=E7=BD=AE=E4=B8=BA?= =?UTF-8?q?35=E5=A4=A9=EF=BC=8C=E5=A2=9E=E5=8A=A0=E8=85=BE=E8=AE=AF?= =?UTF-8?q?=E4=BA=91=E5=88=A0=E9=99=A4=E8=BF=87=E6=9C=9F=E8=AF=81=E4=B9=A6?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=EF=BC=8C=E5=8F=AF=E4=BB=A5=E9=81=BF=E5=85=8D?= =?UTF-8?q?=E8=85=BE=E8=AE=AF=E4=BA=91=E8=BF=87=E6=9C=9F=E8=AF=81=E4=B9=A6?= =?UTF-8?q?=E9=82=AE=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/pipeline/src/plugin/api.ts | 6 + .../src/plugin/cert-plugin/base.ts | 4 +- .../src/plugin/cert-plugin/index.ts | 2 +- .../src/plugin/cert-plugin/lego/index.ts | 2 +- .../src/views/certd/pipeline/crud.tsx | 2 +- .../src/plugins/plugin-tencent/lib/index.ts | 17 ++ .../plugin/delete-expiring-cert/index.ts | 201 ++++++++++++++++++ .../plugins/plugin-tencent/plugin/index.ts | 1 + 8 files changed, 230 insertions(+), 5 deletions(-) create mode 100644 packages/ui/certd-server/src/plugins/plugin-tencent/plugin/delete-expiring-cert/index.ts diff --git a/packages/core/pipeline/src/plugin/api.ts b/packages/core/pipeline/src/plugin/api.ts index 5fd30609..2b86b0e5 100644 --- a/packages/core/pipeline/src/plugin/api.ts +++ b/packages/core/pipeline/src/plugin/api.ts @@ -111,6 +111,12 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin { return this._result.files; } + checkSignal() { + if (this.ctx.signal && this.ctx.signal.aborted) { + throw new Error("用户取消"); + } + } + setCtx(ctx: TaskInstanceContext) { this.ctx = ctx; this.logger = ctx.logger; 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 ec3b7cbe..8efcdf09 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts @@ -61,7 +61,7 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin { @TaskInput({ title: "更新天数", - value: 20, + value: 35, component: { name: "a-input-number", vModel: "value", @@ -212,7 +212,7 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin { this.logger.info("input hash 有变更,检查是否需要重新申请证书"); //判断域名有没有变更 /** - * "renewDays": 20, + * "renewDays": 35, * "certApplyPlugin": "CertApply", * "sslProvider": "letsencrypt", * "privateKeyType": "rsa_2048_pkcs1", 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 2c842da4..435a1a6a 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts @@ -33,7 +33,7 @@ export type DomainsVerifyPlanInput = { desc: "免费通配符域名证书申请,支持多个域名打到同一个证书上", default: { input: { - renewDays: 20, + renewDays: 35, forceUpdate: false, }, strategy: { diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/lego/index.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/lego/index.ts index 881a7ac7..a2c13c71 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/lego/index.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/lego/index.ts @@ -17,7 +17,7 @@ export type { CertInfo }; desc: "支持海量DNS解析提供商,推荐使用,一样的免费通配符域名证书申请,支持多个域名打到同一个证书上", default: { input: { - renewDays: 20, + renewDays: 35, forceUpdate: false, }, strategy: { 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 7b83032d..c1632098 100644 --- a/packages/ui/certd-client/src/views/certd/pipeline/crud.tsx +++ b/packages/ui/certd-client/src/views/certd/pipeline/crud.tsx @@ -124,7 +124,7 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp { title: "申请证书", input: { - renewDays: 20, + renewDays: 35, ...form }, strategy: { diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/lib/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/lib/index.ts index 8777c920..8022ed91 100644 --- a/packages/ui/certd-server/src/plugins/plugin-tencent/lib/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/lib/index.ts @@ -55,4 +55,21 @@ export class TencentSslClient { this.checkRet(res); return res; } + + async DescribeCertificates(params: any) { + const client = await this.getSslClient(); + const res = await client.DescribeCertificates(params); + this.checkRet(res); + return res; + } + + async doRequest(action: string, params: any) { + const client = await this.getSslClient(); + if (!client[action]) { + throw new Error(`action ${action} not found`); + } + const res = await client[action](params); + this.checkRet(res); + return res; + } } diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/delete-expiring-cert/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/delete-expiring-cert/index.ts new file mode 100644 index 00000000..981df38d --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/delete-expiring-cert/index.ts @@ -0,0 +1,201 @@ +import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline'; +import { AbstractPlusTaskPlugin, TencentAccess } from '@certd/plugin-plus'; +import { TencentSslClient } from '../../lib/index.js'; +import dayjs from 'dayjs'; +import { remove } from 'lodash-es'; + +@IsTaskPlugin({ + name: 'TencentDeleteExpiringCert', + title: '删除腾讯云即将过期证书', + icon: 'svg:icon-tencentcloud', + group: pluginGroups.tencent.key, + desc: '仅删除未使用的证书', + default: { + strategy: { + runStrategy: RunStrategy.AlwaysRun, + }, + }, + needPlus: true, +}) +export class TencentDeleteExpiringCert extends AbstractPlusTaskPlugin { + @TaskInput({ + title: 'Access提供者', + helper: 'access 授权', + component: { + name: 'access-selector', + type: 'tencent', + }, + required: true, + }) + accessId!: string; + + @TaskInput({ + title: '关键字筛选', + helper: '仅匹配ID、备注名称、域名包含关键字的证书,可以不填', + required: false, + component: { + name: 'a-input', + }, + }) + searchKey!: string; + + @TaskInput({ + title: '最大删除数量', + helper: '单次运行最大删除数量', + value: 100, + component: { + name: 'a-input-number', + vModel: 'value', + }, + required: true, + }) + maxCount!: number; + + @TaskInput({ + title: '即将过期天数', + helper: '仅删除有效期小于此天数的证书,\n要避免腾讯云的证书过期邮件提醒,此处需要设置为30,同时申请证书任务的更新天数要设置为35', + value: 10, + component: { + name: 'a-input-number', + vModel: 'value', + }, + required: true, + }) + expiringDays!: number; + + @TaskInput({ + title: '检查超时时间', + helper: '检查删除任务结果超时时间,单位分钟', + value: 10, + component: { + name: 'a-input-number', + vModel: 'value', + }, + required: true, + }) + checkTimeout!: number; + + async onInstance() {} + + async execute(): Promise { + const access = await this.accessService.getById(this.accessId); + const sslClient = new TencentSslClient({ + access, + logger: this.logger, + }); + + const params = { + Limit: this.maxCount ?? 100, + SearchKey: this.searchKey, + ExpirationSort: 'ASC', + FilterSource: 'upload', + // FilterExpiring: 1, + }; + const res = await sslClient.DescribeCertificates(params); + let certificates = res?.Certificates; + if (!certificates && !certificates.length) { + this.logger.info('没有找到证书'); + return; + } + + certificates = certificates.filter((item: any) => { + const endTime = item.CertEndTime; + return dayjs(endTime).add(this.expiringDays, 'day').isBefore(dayjs()); + }); + for (const certificate of certificates) { + this.logger.info(`证书ID:${certificate.CertificateId}, 过期时间:${certificate.CertEndTime},Alias:${certificate.Alias},证书域名:${certificate.Domain}`); + } + this.logger.info(`即将过期的证书数量:${certificates.length}`); + if (certificates.length === 0) { + this.logger.info('没有即将过期的证书, 无需删除'); + return; + } + const certIds = certificates.map((cert: any) => cert.CertificateId); + + const deleteRes = await sslClient.doRequest('DeleteCertificates', { + CertificateIds: certIds, + IsSync: true, + }); + this.logger.info('删除任务已提交: ', JSON.stringify(deleteRes)); + const ids = deleteRes?.CertTaskIds; + if (!ids && !ids.length) { + this.logger.error('没有找到任务ID'); + return; + } + const taskIds = ids.map((id: any) => id.TaskId); + const startTime = Date.now(); + const results = {}; + + const statusCount = { + success: 0, + failed: 0, + unauthorized: 0, + unbind: 0, + timeout: 0, + }; + const total = taskIds.length; + + while (Date.now() < startTime + this.checkTimeout * 60 * 1000) { + this.checkSignal(); + const taskResultRes = await sslClient.doRequest('DescribeDeleteCertificatesTaskResult', { + TaskIds: taskIds, + }); + const result = taskResultRes.DeleteTaskResult; + if (!result || result.length === 0) { + this.logger.info('暂未获取到有效的任务结果'); + continue; + } + for (const item of result) { + //遍历结果 + const status = item.Status; + if (status !== 0) { + remove(taskIds, id => id === item.TaskId); + } + // Status : 0表示任务进行中、 1表示任务成功、 2表示任务失败、3表示未授权服务角色导致任务失败、4表示有未解绑的云资源导致任务失败、5表示查询关联云资源超时导致任务失败 + if (status === 0) { + this.logger.info(`任务${item.TaskId}<${item.CertId}>: 进行中`); + } else if (status === 1) { + this.logger.info(`任务${item.TaskId}<${item.CertId}>: 成功`); + results[item.TaskId] = '成功'; + statusCount.success++; + } else if (status === 2) { + this.logger.error(`任务${item.TaskId}<${item.CertId}>: 失败`); + results[item.TaskId] = '失败'; + statusCount.failed++; + } else if (status === 3) { + this.logger.error(`任务${item.TaskId}<${item.CertId}>: 未授权服务角色导致任务失败`); + results[item.TaskId] = '未授权服务角色导致任务失败'; + statusCount.unauthorized++; + } else if (status === 4) { + this.logger.error(`任务${item.TaskId}<${item.CertId}>: 有未解绑的云资源导致任务失败`); + results[item.TaskId] = '有未解绑的云资源导致任务失败'; + statusCount.unbind++; + } else if (status === 5) { + this.logger.error(`任务${item.TaskId}<${item.CertId}>: 查询关联云资源超时导致任务失败`); + results[item.TaskId] = '查询关联云资源超时导致任务失败'; + statusCount.timeout++; + } else { + this.logger.info(`任务${item.TaskId}<${item.CertId}>: 未知状态:${status}`); + statusCount.failed++; + } + } + this.logger.info( + // eslint-disable-next-line max-len + `任务总数:${total}, 进行中:${taskIds.length}, 成功:${statusCount.success}, 未授权服务角色导致失败:${statusCount.unauthorized}, 未解绑关联资源失败:${statusCount.unbind}, 查询关联资源超时:${statusCount.timeout},未知原因失败:${statusCount.failed}` + ); + if (taskIds.length === 0) { + this.logger.info('任务已全部完成'); + + if (statusCount.unauthorized > 0) { + throw new Error('有未授权服务角色导致任务失败,需给Access授权服务角色SSL_QCSLinkedRoleInReplaceLoadCertificate'); + } + + return; + } + await this.ctx.utils.sleep(10000); + } + this.logger.error('检查任务结果超时', JSON.stringify(results)); + } +} + +new TencentDeleteExpiringCert(); diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/index.ts index aadbe339..758b2d3a 100644 --- a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/index.ts @@ -5,3 +5,4 @@ export * from './deploy-to-cdn-v2/index.js'; export * from './upload-to-tencent/index.js'; export * from './deploy-to-cos/index.js'; export * from './deploy-to-eo/index.js'; +export * from './delete-expiring-cert/index.js';