diff --git a/docker/run/docker-compose.yaml b/docker/run/docker-compose.yaml
index cf5bd70ff..420cf0b1c 100644
--- a/docker/run/docker-compose.yaml
+++ b/docker/run/docker-compose.yaml
@@ -28,18 +28,14 @@ services:
# 设置环境变量即可自定义certd配置
# 配置项见: packages/ui/certd-server/src/config/config.default.ts
# 配置规则: certd_ + 配置项, 点号用_代替
-
- # ↓↓↓↓ ------------------------------------ 这里可以设置http代理
- #- HTTPS_PROXY=http://xxxxxx:xx
- #- HTTP_PROXY=http://xxxxxx:xx
# ↓↓↓↓ ----------------------------- 如果忘记管理员密码,可以设置为true,重启之后,管理员密码将改成123456,然后请及时修改回false
- certd_system_resetAdminPasswd=false
- # ↓↓↓↓ -------------------------- 如果设置为true,启动后所有配置了cron的流水线任务都将被立即触发一次
- - certd_cron_immediateTriggerOnce=false
- # ↓↓↓↓ -------------------------------- 配置证书和key,则表示https方式启动,使用https协议访问,https://your.domain:7001
- #- certd_koa_key=./data/ssl/cert.key
- #- certd_koa_cert=./data/ssl/cert.crt
-
+ # ↓↓↓↓ -------------------------------- 默认同时启动https,https访问地址https://your.domain:7002
+ #- certd_https_key=./data/ssl/cert.key
+ #- certd_https_cert=./data/ssl/cert.crt
+ #- certd_https_enabled=true
+ #- certd_https_port=7002
+ -
# ↓↓↓↓ ------------------------------- 使用postgresql数据库
# - certd_flyway_scriptDir=./db/migration-pg # 升级脚本目录
# - certd_typeorm_dataSource_default_type=postgres # 数据库类型
diff --git a/docs/guide/use/tencent/images/delete.png b/docs/guide/use/tencent/images/delete.png
new file mode 100644
index 000000000..c480c97ab
Binary files /dev/null and b/docs/guide/use/tencent/images/delete.png differ
diff --git a/docs/guide/use/tencent/images/delete2.png b/docs/guide/use/tencent/images/delete2.png
new file mode 100644
index 000000000..67ef7895a
Binary files /dev/null and b/docs/guide/use/tencent/images/delete2.png differ
diff --git a/docs/guide/use/tencent/index.md b/docs/guide/use/tencent/index.md
index 2dcebee6a..9dbf6a399 100644
--- a/docs/guide/use/tencent/index.md
+++ b/docs/guide/use/tencent/index.md
@@ -6,3 +6,16 @@
打开 https://console.cloud.tencent.com/cam/capi
然后按如下方式获取腾讯云的API密钥

+
+
+## 如何避免收到腾讯云证书过期邮件
+
+腾讯云在证书有效期还剩28天时会发送过期通知邮件
+您可以通过配置“腾讯云过期证书删除”任务来避免收到此类邮件。
+
+
+
+注意点:
+> 1. 选择腾讯云授权,需授权`服务角色SSL_QCSLinkedRoleInReplaceLoadCertificate`权限
+> 2. `1.26.14`版本之前Certd创建的证书流水线默认是到期前20天才更新证书,需要将之前创建的证书申请任务的更新天数修改为35天,保证删除之前就已经替换掉即将过期证书
+
\ No newline at end of file
diff --git a/packages/core/pipeline/src/plugin/api.ts b/packages/core/pipeline/src/plugin/api.ts
index 5fd306091..2b86b0e5e 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 ec3b7cbe7..8efcdf09c 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 2c842da4c..435a1a6ad 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 881a7ac7b..a2c13c712 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/public/static/images/logo/logo.png b/packages/ui/certd-client/public/static/images/logo/logo.png
new file mode 100644
index 000000000..cb42f7473
Binary files /dev/null and b/packages/ui/certd-client/public/static/images/logo/logo.png differ
diff --git a/packages/ui/certd-client/src/layout/layout-framework.vue b/packages/ui/certd-client/src/layout/layout-framework.vue
index d399ca7c7..e764310f9 100644
--- a/packages/ui/certd-client/src/layout/layout-framework.vue
+++ b/packages/ui/certd-client/src/layout/layout-framework.vue
@@ -252,8 +252,6 @@ const { token } = useToken();
justify-content: flex-end;
align-items: center;
display: flex;
- }
- .header-menu {
flex: 1;
}
}
diff --git a/packages/ui/certd-client/src/style/common.less b/packages/ui/certd-client/src/style/common.less
index 7a4fa9213..a33cacbd1 100644
--- a/packages/ui/certd-client/src/style/common.less
+++ b/packages/ui/certd-client/src/style/common.less
@@ -178,6 +178,14 @@ h1, h2, h3, h4, h5, h6 {
color: #1890ff;
}
+.color-red {
+ color: red;
+}
+
+.color-green {
+ color: green;
+}
+
.iconify{
//font-size: 16px;
}
diff --git a/packages/ui/certd-client/src/views/certd/access/access-selector/access/secret-plain-getter.vue b/packages/ui/certd-client/src/views/certd/access/access-selector/access/secret-plain-getter.vue
new file mode 100644
index 000000000..cf05c662f
--- /dev/null
+++ b/packages/ui/certd-client/src/views/certd/access/access-selector/access/secret-plain-getter.vue
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
diff --git a/packages/ui/certd-client/src/views/certd/access/api.ts b/packages/ui/certd-client/src/views/certd/access/api.ts
index 903f9c316..ca9d47c62 100644
--- a/packages/ui/certd-client/src/views/certd/access/api.ts
+++ b/packages/ui/certd-client/src/views/certd/access/api.ts
@@ -43,6 +43,14 @@ export function createAccessApi(from = "user") {
});
},
+ async GetSecretPlain(id: number, key: string) {
+ return await request({
+ url: apiPrefix + "/getSecretPlain",
+ method: "post",
+ data: { id, key }
+ });
+ },
+
async GetProviderDefine(type: string) {
return await request({
url: apiPrefix + "/define",
diff --git a/packages/ui/certd-client/src/views/certd/access/common.tsx b/packages/ui/certd-client/src/views/certd/access/common.tsx
index 99258f648..5b2c3ebec 100644
--- a/packages/ui/certd-client/src/views/certd/access/common.tsx
+++ b/packages/ui/certd-client/src/views/certd/access/common.tsx
@@ -1,9 +1,11 @@
import { ColumnCompositionProps, dict } from "@fast-crud/fast-crud";
-import { computed, ref, toRef } from "vue";
+import { computed, provide, ref, toRef } from "vue";
import { useReference } from "/@/use/use-refrence";
import { forEach, get, merge, set } from "lodash-es";
+import SecretPlainGetter from "/@/views/certd/access/access-selector/access/secret-plain-getter.vue";
export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
+ provide("accessApi", api);
const AccessTypeDictRef = dict({
url: "/pi/access/accessTypeDict"
});
@@ -32,6 +34,13 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
};
const column = merge({ title: key }, defaultPluginConfig, field);
+ if (value.encrypt === true) {
+ column.suffixRender = (scope: { form: any; key: string }) => {
+ const { form, key } = scope;
+ const inputKey = scope.key.replace("access.", "");
+ return ;
+ };
+ }
//eval
useReference(column);
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 7b83032db..c1632098e 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/controller/pipeline/access-controller.ts b/packages/ui/certd-server/src/controller/pipeline/access-controller.ts
index 3ee6e7372..4b1df6831 100644
--- a/packages/ui/certd-server/src/controller/pipeline/access-controller.ts
+++ b/packages/ui/certd-server/src/controller/pipeline/access-controller.ts
@@ -67,6 +67,12 @@ export class AccessController extends CrudController {
return this.ok(access);
}
+ @Post('/getSecretPlain', { summary: Constants.per.authOnly })
+ async getSecretPlain(@Body(ALL) body: { id: number; key: string }) {
+ const value = await this.service.getById(body.id, this.getUserId());
+ return this.ok(value[body.key]);
+ }
+
@Post('/accessTypeDict', { summary: Constants.per.authOnly })
async getAccessTypeDict() {
const list = this.service.getDefineList();
diff --git a/packages/ui/certd-server/src/middleware/reset-passwd/middleware.ts b/packages/ui/certd-server/src/middleware/reset-passwd/middleware.ts
index 56fa7bbbe..eba30c11e 100644
--- a/packages/ui/certd-server/src/middleware/reset-passwd/middleware.ts
+++ b/packages/ui/certd-server/src/middleware/reset-passwd/middleware.ts
@@ -30,6 +30,7 @@ export class ResetPasswdMiddleware implements IWebMiddleware {
logger.info('开始重置1号管理员用户的密码');
const newPasswd = '123456';
await this.userService.resetPassword(1, newPasswd);
+ await this.userService.updateStatus(1, 1);
logger.info(`重置1号管理员用户的密码完成,新密码为:${newPasswd}`);
}
}
diff --git a/packages/ui/certd-server/src/modules/sys/authority/service/user-service.ts b/packages/ui/certd-server/src/modules/sys/authority/service/user-service.ts
index a6a0d27b8..5748a6f42 100644
--- a/packages/ui/certd-server/src/modules/sys/authority/service/user-service.ts
+++ b/packages/ui/certd-server/src/modules/sys/authority/service/user-service.ts
@@ -198,6 +198,9 @@ export class UserService extends BaseService {
}
async resetPassword(userId: any, newPasswd: string) {
+ if (!userId) {
+ throw new CommonException('userId不能为空');
+ }
const param = {
id: userId,
password: newPasswd,
@@ -210,15 +213,19 @@ export class UserService extends BaseService {
ids = ids.split(',');
ids = ids.map(id => parseInt(id));
}
- if (ids instanceof Array) {
- if (ids.includes(1)) {
- throw new CommonException('不能删除管理员');
- }
+ if (ids.length === 0) {
+ return;
+ }
+ if (ids.includes(1)) {
+ throw new CommonException('不能删除管理员');
}
await super.delete(ids);
}
async isAdmin(userId: any) {
+ if (!userId) {
+ throw new CommonException('userId不能为空');
+ }
const userRoles = await this.userRoleService.find({
where: {
userId,
@@ -229,4 +236,13 @@ export class UserService extends BaseService {
return true;
}
}
+
+ async updateStatus(id: number, status: number) {
+ if (!id) {
+ throw new CommonException('userId不能为空');
+ }
+ await this.repository.update(id, {
+ status,
+ });
+ }
}
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 8777c920d..8022ed914 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 000000000..998274930
--- /dev/null
+++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/delete-expiring-cert/index.ts
@@ -0,0 +1,202 @@
+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注意:`1.26.14`版本之前Certd创建的证书流水线默认是到期前20天才更新证书,需要将之前创建的证书申请任务的更新天数改为35天,保证删除之前就已经替换掉即将过期证书',
+ value: 30,
+ 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 aadbe339d..758b2d3ae 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';