From f126f9f932d37fa01fff1accc7bdd17d349f8db5 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Wed, 23 Oct 2024 16:33:53 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E6=96=B0=E5=A2=9E=E9=83=A8=E7=BD=B2?= =?UTF-8?q?=E5=88=B0=E7=99=BE=E5=BA=A6=E4=BA=91CDN=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/guide/feature/cname/index.md | 1 + packages/core/basic/src/utils/util.request.ts | 2 +- packages/ui/certd-client/package.json | 1 + .../components/tutorial/tutorial-steps.vue | 2 +- .../src/components/vip-button/index.vue | 4 +- .../src/store/modules/settings.ts | 2 +- .../src/views/sys/cname/provider/index.vue | 1 + .../pipeline/service/pipeline-service.ts | 2 +- .../src/plugins/plugin-tencent/lib/index.ts | 59 +++++++ .../plugin/deploy-to-cos/index.ts | 163 ++++++++++++++++++ 10 files changed, 231 insertions(+), 6 deletions(-) create mode 100644 packages/ui/certd-server/src/plugins/plugin-tencent/lib/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cos/index.ts diff --git a/docs/guide/feature/cname/index.md b/docs/guide/feature/cname/index.md index 85c5c601..f930f7a9 100644 --- a/docs/guide/feature/cname/index.md +++ b/docs/guide/feature/cname/index.md @@ -36,6 +36,7 @@ _acme-challenge.cert.com ---> xxxxx.cname.proxy.com ----> txt-record-abcdefg ![](./images/cname3.png) ![](./images/cname4.png) 4. 申请过程中,Certd会在`xxxxxx.cname.proxy.com`下自动添加TXT记录。 +5. 到此即可自动化申请证书了 diff --git a/packages/core/basic/src/utils/util.request.ts b/packages/core/basic/src/utils/util.request.ts index 1167f885..30503771 100644 --- a/packages/core/basic/src/utils/util.request.ts +++ b/packages/core/basic/src/utils/util.request.ts @@ -182,7 +182,7 @@ export function createAxiosService({ logger }: { logger: Logger }) { export const http = createAxiosService({ logger }) as HttpClient; export type HttpClientResponse = any; -export type HttpRequestConfig = { +export type HttpRequestConfig = { skipSslVerify?: boolean; skipCheckRes?: boolean; logParams?: boolean; diff --git a/packages/ui/certd-client/package.json b/packages/ui/certd-client/package.json index 5eaf7f5f..564f7086 100644 --- a/packages/ui/certd-client/package.json +++ b/packages/ui/certd-client/package.json @@ -42,6 +42,7 @@ "china-division": "^2.7.0", "core-js": "^3.36.0", "cos-js-sdk-v5": "^1.7.0", + "cron-parser": "^4.9.0", "cropperjs": "^1.6.1", "dayjs": "^1.11.10", "highlight.js": "^11.9.0", diff --git a/packages/ui/certd-client/src/components/tutorial/tutorial-steps.vue b/packages/ui/certd-client/src/components/tutorial/tutorial-steps.vue index 2f22dc71..bdd2fbaf 100644 --- a/packages/ui/certd-client/src/components/tutorial/tutorial-steps.vue +++ b/packages/ui/certd-client/src/components/tutorial/tutorial-steps.vue @@ -199,7 +199,7 @@ const steps = ref([ { image: "/static/doc/images/15-1-email.png", title: "设置邮件通知", - descriptions: ["建议选择监听'错误时'和'错误转成功'两种即可,在意外失败时可以尽快去排查问题,(免费版需要配置邮件服务器)"] + descriptions: ["建议选择监听'错误时'和'错误转成功'两种即可,在意外失败时可以尽快去排查问题,(基础版需要配置邮件服务器)"] }, { title: "教程结束", diff --git a/packages/ui/certd-client/src/components/vip-button/index.vue b/packages/ui/certd-client/src/components/vip-button/index.vue index 496ed160..4f501770 100644 --- a/packages/ui/certd-client/src/components/vip-button/index.vue +++ b/packages/ui/certd-client/src/components/vip-button/index.vue @@ -59,7 +59,7 @@ const text = computed(() => { title: "此为专业版功能" }, nav: { - name: "免费版", + name: "基础版", title: "升级专业版,享受更多VIP特权" } } @@ -93,7 +93,7 @@ const formState = reactive({ const vipTypeDefine = { free: { - title: "免费版", + title: "基础版", type: "free", privilege: ["证书申请功能无限制", "证书流水线数量10条", "常用的主机、cdn等部署插件"] }, diff --git a/packages/ui/certd-client/src/store/modules/settings.ts b/packages/ui/certd-client/src/store/modules/settings.ts index a6036676..baf72fff 100644 --- a/packages/ui/certd-client/src/store/modules/settings.ts +++ b/packages/ui/certd-client/src/store/modules/settings.ts @@ -111,7 +111,7 @@ export const useSettingStore = defineStore({ }, vipLabel(): string { const vipLabelMap: any = { - free: "免费版", + free: "基础版", plus: "专业版", comm: "商业版" }; diff --git a/packages/ui/certd-client/src/views/sys/cname/provider/index.vue b/packages/ui/certd-client/src/views/sys/cname/provider/index.vue index 75848226..028f63df 100644 --- a/packages/ui/certd-client/src/views/sys/cname/provider/index.vue +++ b/packages/ui/certd-client/src/views/sys/cname/provider/index.vue @@ -5,6 +5,7 @@ CNAME服务配置 此处配置的域名作为其他域名校验的代理,当别的域名需要申请证书时,通过CNAME映射到此域名上来验证所有权。好处是任何域名都可以通过此方式申请证书,也无需填写AccessSecret。 + CNAME功能原理及使用说明 diff --git a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts index 5bdeb355..038aa520 100644 --- a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts +++ b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts @@ -128,7 +128,7 @@ export class PipelineService extends BaseService { count += 1; } if (count > freeCount) { - throw new NeedVIPException('免费版最多只能创建10个pipeline'); + throw new NeedVIPException('基础版最多只能创建10个pipeline'); } } if (!isUpdate) { 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 new file mode 100644 index 00000000..81b76498 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/lib/index.ts @@ -0,0 +1,59 @@ +import { TencentAccess } from '@certd/plugin-plus'; +import { CertInfo } from '@certd/plugin-cert'; +import { ILogger } from '@certd/pipeline'; + +export class TencentSslClient { + access: TencentAccess; + logger: ILogger; + region?: string; + constructor(opts: { access: TencentAccess; logger: ILogger; region?: string }) { + this.access = opts.access; + this.logger = opts.logger; + this.region = opts.region; + } + async getSslClient(): Promise { + const sdk = await import('tencentcloud-sdk-nodejs/tencentcloud/services/ssl/v20191205/index.js'); + const SslClient = sdk.v20191205.Client; + + const clientConfig = { + credential: { + secretId: this.access.secretId, + secretKey: this.access.secretKey, + }, + region: this.region, + profile: { + httpProfile: { + endpoint: 'ssl.tencentcloudapi.com', + }, + }, + }; + + return new SslClient(clientConfig); + } + + checkRet(ret: any) { + if (!ret || ret.Error) { + throw new Error('请求失败:' + ret.Error.Code + ',' + ret.Error.Message); + } + } + + async uploadToTencent(opts: { certName: string; cert: CertInfo }): Promise { + const client = await this.getSslClient(); + const params = { + CertificatePublicKey: opts.cert.crt, + CertificatePrivateKey: opts.cert.key, + Alias: opts.certName, + }; + const ret = await client.UploadCertificate(params); + this.checkRet(ret); + this.logger.info('证书上传成功:tencentCertId=', ret.CertificateId); + return ret.CertificateId; + } + + async deployCertificateInstance(params: any) { + const client = await this.getSslClient(); + const res = await client.DeployCertificateInstance(params); + this.checkRet(res); + return res; + } +} diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cos/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cos/index.ts new file mode 100644 index 00000000..0a4d700b --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cos/index.ts @@ -0,0 +1,163 @@ +import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline'; +import { CertInfo } from '@certd/plugin-cert'; +import { AbstractPlusTaskPlugin, createRemoteSelectInputDefine } from '@certd/plugin-plus'; +import { TencentSslClient } from '../../lib/index.js'; + +@IsTaskPlugin({ + name: 'DeployCertToTencentCosPlugin', + title: '部署证书到腾讯云COS', + needPlus: true, + icon: 'svg:icon-tencentcloud', + group: pluginGroups.tencent.key, + desc: '部署到腾讯云COS源站域名证书', + deprecated: '暂不可用', + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, +}) +export class DeployCertToTencentCosPlugin extends AbstractPlusTaskPlugin { + /** + * AccessProvider的id + */ + @TaskInput({ + title: 'Access授权', + helper: 'access授权', + component: { + name: 'access-selector', + type: 'tencent', + }, + required: true, + }) + accessId!: string; + + @TaskInput({ + title: '存储桶名称', + helper: '请输入存储桶名称', + }) + bucket!: string; + + @TaskInput({ + title: '所在地域', + helper: '存储桶所在地域', + component: { + name: 'a-auto-complete', + vModel: 'value', + options: [ + { value: '', label: '--------中国大陆地区-------', disabled: true }, + { value: 'ap-beijing-1', label: '北京1区' }, + { value: 'ap-beijing', label: '北京' }, + { value: 'ap-nanjing', label: '南京' }, + { value: 'ap-shanghai', label: '上海' }, + { value: 'ap-guangzhou', label: '广州' }, + { value: 'ap-chengdu', label: '成都' }, + { value: 'ap-chongqing', label: '重庆' }, + { value: 'ap-shenzhen-fsi', label: '深圳金融' }, + { value: 'ap-shanghai-fsi', label: '上海金融' }, + { value: 'ap-beijing-fsi', label: '北京金融' }, + { value: '', label: '--------中国香港及境外-------', disabled: true }, + { value: 'ap-hongkong', label: '中国香港' }, + { value: 'ap-singapore', label: '新加坡' }, + { value: 'ap-mumbai', label: '孟买' }, + { value: 'ap-jakarta', label: '雅加达' }, + { value: 'ap-seoul', label: '首尔' }, + { value: 'ap-bangkok', label: '曼谷' }, + { value: 'ap-tokyo', label: '东京' }, + { value: 'na-siliconvalley', label: '硅谷' }, + { value: 'na-ashburn', label: '弗吉尼亚' }, + { value: 'sa-saopaulo', label: '圣保罗' }, + { value: 'eu-frankfurt', label: '法兰克福' }, + ], + }, + }) + region!: string; + + // @TaskInput(createCertDomainGetterInputDefine()) + // certDomains!: string[]; + + @TaskInput( + createRemoteSelectInputDefine({ + title: 'COS域名', + helper: '请选择域名', + typeName: DeployCertToTencentCosPlugin.name, + action: DeployCertToTencentCosPlugin.prototype.onGetDomainList.name, + watches: ['bucket', 'region'], + }) + ) + domains!: string | string[]; + + @TaskInput({ + title: '域名证书', + helper: '请选择前置任务输出的域名证书,或者选择前置任务“上传证书到腾讯云”任务的证书ID', + component: { + name: 'output-selector', + from: ['CertApply', 'CertApplyLego', 'UploadCertToTencent'], + }, + required: true, + }) + cert!: CertInfo | string; + + async onInstance() {} + + async execute(): Promise { + const access = await this.accessService.getById(this.accessId); + + const client = new TencentSslClient({ + access, + logger: this.logger, + region: this.region, + }); + + let tencentCertId: string = this.cert as string; + if (typeof this.cert !== 'string') { + tencentCertId = await client.uploadToTencent({ + certName: this.appendTimeSuffix('certd'), + cert: this.cert, + }); + } + + const params = { + CertificateId: tencentCertId, + ResourceType: 'cos', + Status: 1, + InstanceIdList: [`${this.bucket}#${this.region}#${this.domains}`], + }; + + const res = await client.deployCertificateInstance(params); + this.logger.info('部署成功', res); + } + + async onGetDomainList(data: any) { + const access = await this.accessService.getById(this.accessId); + + const cosv5 = await import('cos-nodejs-sdk-v5'); + const cos = new cosv5.default({ + SecretId: access.secretId, + SecretKey: access.secretKey, + }); + if (!this.bucket) { + throw new Error('存储桶名称不能为空'); + } + if (!this.region) { + throw new Error('所在地域不能为空'); + } + + const res = await cos.getBucketDomain({ + Bucket: this.bucket, + /** 存储桶所在地域 @see https://cloud.tencent.com/document/product/436/6224 */ + Region: this.region, + }); + + this.ctx.logger.info('获取域名列表:', res); + + return res.DomainRule.map((item: any) => { + return { + label: item.Name, + value: item.Name, + }; + }); + } +} + +// new DeployCertToTencentCosPlugin()