From fbb9a47e8f7bb805289b9ee64bd46ffee0f01c06 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Sat, 15 Jun 2024 02:17:34 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E6=94=AF=E6=8C=81cloudflare=E5=9F=9F?= =?UTF-8?q?=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/acme-client/src/auto.js | 57 +++++---- packages/core/pipeline/src/core/executor.ts | 5 +- .../core/pipeline/src/utils/util.request.ts | 13 +- .../plugin-cert/src/dns-provider/api.ts | 11 +- .../plugin-cert/src/dns-provider/base.ts | 6 +- .../src/plugin/cert-plugin/index.ts | 5 +- .../pipeline/component/task-view/index.vue | 5 +- .../dns-provider/aliyun-dns-provider.ts | 2 +- .../src/plugins/plugin-cloudflare/access.ts | 2 +- .../plugins/plugin-cloudflare/dns-provider.ts | 118 +++++++++++++----- .../src/plugins/plugin-demo/access.ts | 4 +- .../src/plugins/plugin-demo/dns-provider.ts | 80 ++++++++---- .../dns-provider/huawei-dns-provider.ts | 2 +- .../dns-provider/dnspod-dns-provider.ts | 2 +- 14 files changed, 206 insertions(+), 106 deletions(-) diff --git a/packages/core/acme-client/src/auto.js b/packages/core/acme-client/src/auto.js index 68fb60563..c15a47c11 100644 --- a/packages/core/acme-client/src/auto.js +++ b/packages/core/acme-client/src/auto.js @@ -119,6 +119,7 @@ module.exports = async function(client, userOpts) { try { recordItem = await opts.challengeCreateFn(authz, challenge, keyAuthorization); + // throw new Error('测试异常'); /* Challenge verification */ if (opts.skipChallengeVerification === true) { log(`[auto] [${d}] Skipping challenge verification since skipChallengeVerification=true`); @@ -177,30 +178,43 @@ module.exports = async function(client, userOpts) { }); - // let promise = Promise.resolve(); - // function runPromisesSerially(tasks) { - // tasks.forEach((task) => { - // promise = promise.then(task); - // }); - // return promise; - // } - - function runPromiseParallel(tasks) { - return Promise.all(tasks.map((task) => task())); + function runAllPromise(tasks) { + let promise = Promise.resolve(); + tasks.forEach((task) => { + promise = promise.then(task); + }); + return promise; } + // function runPromisePa(tasks) { + // return Promise.all(tasks.map((task) => task())); + // } + try { log('开始challenge'); - await runPromiseParallel(challengePromises); + await runAllPromise(challengePromises); + + log('challenge结束'); + + // log('[auto] Waiting for challenge valid status'); + // await Promise.all(challengePromises); + + /** + * Finalize order and download certificate + */ + + log('[auto] Finalizing order and downloading certificate'); + const finalized = await client.finalizeOrder(order, opts.csr); + return await client.getCertificate(finalized, opts.preferredChain); } catch (e) { - log('challenge失败'); + log('证书申请失败'); throw e; } finally { - log('清理challenge痕迹'); - await runPromiseParallel(clearTasks); + log(`清理challenge痕迹,length:${clearTasks.length}`); + await runAllPromise(clearTasks); } // try { @@ -210,19 +224,4 @@ module.exports = async function(client, userOpts) { // log('清理challenge'); // await Promise.allSettled(clearTasks); // } - - - log('challenge结束'); - - // log('[auto] Waiting for challenge valid status'); - // await Promise.all(challengePromises); - - - /** - * Finalize order and download certificate - */ - - log('[auto] Finalizing order and downloading certificate'); - const finalized = await client.finalizeOrder(order, opts.csr); - return client.getCertificate(finalized, opts.preferredChain); }; diff --git a/packages/core/pipeline/src/core/executor.ts b/packages/core/pipeline/src/core/executor.ts index 2cacfed5a..83a3707e9 100644 --- a/packages/core/pipeline/src/core/executor.ts +++ b/packages/core/pipeline/src/core/executor.ts @@ -6,7 +6,7 @@ import { ContextFactory, IContext } from "./context"; import { IStorage } from "./storage"; import { logger } from "../utils/util.log"; import { Logger } from "log4js"; -import { request } from "../utils/util.request"; +import { createAxiosService } from "../utils/util.request"; import { IAccessService } from "../access"; import { RegistryItem } from "../registry"; import { Decorator } from "../decorator"; @@ -213,11 +213,12 @@ export class Executor { } }); + const http = createAxiosService({ logger: currentLogger }); const taskCtx: TaskInstanceContext = { pipeline: this.pipeline, step, lastStatus, - http: request, + http, logger: currentLogger, accessService: this.options.accessService, emailService: this.options.emailService, diff --git a/packages/core/pipeline/src/utils/util.request.ts b/packages/core/pipeline/src/utils/util.request.ts index cca9f2968..a7a44fcb6 100644 --- a/packages/core/pipeline/src/utils/util.request.ts +++ b/packages/core/pipeline/src/utils/util.request.ts @@ -2,10 +2,11 @@ import axios from "axios"; // @ts-ignore import qs from "qs"; import { logger } from "./util.log"; +import { Logger } from "log4js"; /** * @description 创建请求实例 */ -function createService() { +export function createAxiosService({ logger }: { logger: Logger }) { // 创建一个 axios 实例 const service = axios.create(); // 请求拦截 @@ -18,18 +19,19 @@ function createService() { }); // 序列化请求参数 delete config.formData; } + logger.info(`http request:${config.url},method:${config.method}`); return config; }, (error: Error) => { // 发送失败 - logger.error(error); + logger.error("接口请求失败:", error); return Promise.reject(error); } ); // 响应拦截 service.interceptors.response.use( (response: any) => { - logger.info("http response:", JSON.stringify(response.data)); + logger.info("http response:", JSON.stringify(response?.data)); return response.data; }, (error: any) => { @@ -48,11 +50,12 @@ function createService() { // case 505: error.message = 'HTTP版本不受支持'; break // default: break // } - logger.error("请求出错:", error.response.config.url, error); + logger.error(`请求出错:url:${error?.response?.config.url},method:${error.response.config.method},status:${error?.response?.status}`); + logger.info("返回数据:", JSON.stringify(error?.response?.data)); return Promise.reject(error); } ); return service; } -export const request = createService(); +export const request = createAxiosService({ logger }); diff --git a/packages/plugins/plugin-cert/src/dns-provider/api.ts b/packages/plugins/plugin-cert/src/dns-provider/api.ts index 3c0800b22..d0a05cea5 100644 --- a/packages/plugins/plugin-cert/src/dns-provider/api.ts +++ b/packages/plugins/plugin-cert/src/dns-provider/api.ts @@ -13,8 +13,9 @@ export type CreateRecordOptions = { value: any; domain: string; }; -export type RemoveRecordOptions = CreateRecordOptions & { - record: any; +export type RemoveRecordOptions = CreateRecordOptions & { + // 本次创建的dns解析记录,实际上就是createRecord接口的返回值 + record: T; }; export type DnsProviderContext = { @@ -23,9 +24,9 @@ export type DnsProviderContext = { http: HttpClient; }; -export interface IDnsProvider { +export interface IDnsProvider { onInstance(): Promise; - createRecord(options: CreateRecordOptions): Promise; - removeRecord(options: RemoveRecordOptions): Promise; + createRecord(options: CreateRecordOptions): Promise; + removeRecord(options: RemoveRecordOptions): Promise; setCtx(ctx: DnsProviderContext): void; } diff --git a/packages/plugins/plugin-cert/src/dns-provider/base.ts b/packages/plugins/plugin-cert/src/dns-provider/base.ts index b76883ccc..f984ea746 100644 --- a/packages/plugins/plugin-cert/src/dns-provider/base.ts +++ b/packages/plugins/plugin-cert/src/dns-provider/base.ts @@ -1,15 +1,15 @@ import { CreateRecordOptions, DnsProviderContext, IDnsProvider, RemoveRecordOptions } from "./api"; -export abstract class AbstractDnsProvider implements IDnsProvider { +export abstract class AbstractDnsProvider implements IDnsProvider { ctx!: DnsProviderContext; setCtx(ctx: DnsProviderContext) { this.ctx = ctx; } - abstract createRecord(options: CreateRecordOptions): Promise; + abstract createRecord(options: CreateRecordOptions): Promise; abstract onInstance(): Promise; - abstract removeRecord(options: RemoveRecordOptions): Promise; + abstract removeRecord(options: RemoveRecordOptions): Promise; } 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 58852e8b4..326bc2e9a 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts @@ -107,7 +107,7 @@ export class CertApplyPlugin extends AbstractTaskPlugin { @TaskInput({ title: "CsrInfo", }) - csrInfo: any; + csrInfo!: string; acme!: AcmeService; logger!: Logger; @@ -167,7 +167,6 @@ export class CertApplyPlugin extends AbstractTaskPlugin { /** * 是否更新证书 - * @param input */ async condition() { if (this.forceUpdate) { @@ -220,7 +219,7 @@ export class CertApplyPlugin extends AbstractTaskPlugin { organizationUnit: "IT Department", emailAddress: email, }, - this.csrInfo + this.csrInfo ? JSON.parse(this.csrInfo) : {} ); this.logger.info("开始申请证书,", email, domains); diff --git a/packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/task-view/index.vue b/packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/task-view/index.vue index fe960dff9..037f7ad24 100644 --- a/packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/task-view/index.vue +++ b/packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/task-view/index.vue @@ -56,7 +56,10 @@ export default { for (let node of nodes) { if (currentHistory?.value?.logs != null) { node.logs = computed(() => { - return currentHistory.value.logs[node.node.id] || []; + if(currentHistory?.value?.logs && currentHistory.value?.logs[node.node.id]!= null){ + return currentHistory.value?.logs[node.node.id]; + } + return []; }); } } diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/dns-provider/aliyun-dns-provider.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/dns-provider/aliyun-dns-provider.ts index 2906dc71d..9cdb6e048 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/dns-provider/aliyun-dns-provider.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/dns-provider/aliyun-dns-provider.ts @@ -120,7 +120,7 @@ export class AliyunDnsProvider extends AbstractDnsProvider{ throw e; } } - async removeRecord(options: RemoveRecordOptions): Promise { + async removeRecord(options: RemoveRecordOptions): Promise { const { fullRecord, value, record } = options; const params = { RegionId: 'cn-hangzhou', diff --git a/packages/ui/certd-server/src/plugins/plugin-cloudflare/access.ts b/packages/ui/certd-server/src/plugins/plugin-cloudflare/access.ts index 839fb4b19..cf9cd79e1 100644 --- a/packages/ui/certd-server/src/plugins/plugin-cloudflare/access.ts +++ b/packages/ui/certd-server/src/plugins/plugin-cloudflare/access.ts @@ -6,7 +6,7 @@ import { IsAccess, AccessInput } from '@certd/pipeline'; */ @IsAccess({ name: 'cloudflare', - title: '授权插件示例', + title: 'cloudflare授权', desc: '', }) export class CloudflareAccess { diff --git a/packages/ui/certd-server/src/plugins/plugin-cloudflare/dns-provider.ts b/packages/ui/certd-server/src/plugins/plugin-cloudflare/dns-provider.ts index 039b82763..87506545a 100644 --- a/packages/ui/certd-server/src/plugins/plugin-cloudflare/dns-provider.ts +++ b/packages/ui/certd-server/src/plugins/plugin-cloudflare/dns-provider.ts @@ -2,62 +2,118 @@ import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOp import { Autowire, HttpClient, ILogger } from "@certd/pipeline"; import { CloudflareAccess } from "./access"; -// TODO 这里注册一个dnsProvider +export type CloudflareRecord = { + id: string; + type: string; + name: string; + content: string; + ttl: number; + proxied: boolean; + zone_id: string; + zone_name: string; + created_on: string; + modified_on: string; +}; + + +// 这里通过IsDnsProvider注册一个dnsProvider @IsDnsProvider({ name: 'cloudflare', title: 'cloudflare', - desc: 'cloudflare dns provider示例', + desc: 'cloudflare dns provider', + // 这里是对应的 cloudflare的access类型名称 accessType: 'cloudflare', }) -export class CloudflareDnsProvider extends AbstractDnsProvider{ +export class CloudflareDnsProvider extends AbstractDnsProvider{ + // 通过Autowire传递context @Autowire() logger! : ILogger; access!: CloudflareAccess; http!: HttpClient; async onInstance() { //一些初始化的操作 + // 也可以通过ctx成员变量传递context, 与Autowire效果一样 this.access = this.ctx.access as CloudflareAccess; this.http = this.ctx.http } + async getZoneId(domain:string){ + const url = `https://api.cloudflare.com/client/v4/zones?name=${domain}`; + const res = await this.doRequestApi(url,null,"get"); + return res.result[0].id + } + + + private async doRequestApi(url: string,data:any = null,method:string="post") { + const res = await this.http.request({ + url, + method, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.access.apiToken}`, + }, + data + }); + if (!res.success) { + throw new Error(`${JSON.stringify(res.errors)}`); + } + return res + } + /** - * curl --request POST \ - * --url https://api.cloudflare.com/client/v4/zones/zone_id/dns_records \ - * --header 'Content-Type: application/json' \ - * --header 'X-Auth-Email: ' \ - * --data '{ - * "content": "198.51.100.4", - * "name": "example.com", - * "proxied": false, - * "type": "A", - * "comment": "Domain verification record", - * "tags": [ - * "owner:dns-team" - * ], - * "ttl": 60 - * }' + * 创建dns解析记录,用于验证域名所有权 */ - async createRecord(options: CreateRecordOptions): Promise { + async createRecord(options: CreateRecordOptions): Promise { + + /** + * fullRecord: '_acme-challenge.test.example.com', + * value: 一串uuid + * type: 'TXT', + * domain: 'example.com' + */ const { fullRecord, value, type,domain } = options; this.logger.info('添加域名解析:', fullRecord, value, type,domain); - this.http.post('https://api.cloudflare.com/client/v4/zones/zone_id/dns_records') + const zoneId = await this.getZoneId(domain); + this.logger.info('获取zoneId成功:',zoneId) - //TODO 然后调用接口,创建txt类型的dns解析记录 - // .. 这里调用对应平台的后台接口 - const access = this.access; - this.logger.debug('access', access); + // 给domain下创建txt类型的dns解析记录,fullRecord + let url = `https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records`; + const res = await this.doRequestApi(url, { + content: value, + name: fullRecord, + type: type, + ttl: 60, + }) + const record = res.result as CloudflareRecord; + this.logger.info(`添加域名解析成功:fullRecord=${fullRecord},value=${value}`); + this.logger.info(`dns解析记录:${JSON.stringify(record)}`,) + + //本接口需要返回本次创建的dns解析记录,这个记录会在删除的时候用到 + return record } - async removeRecord(options: RemoveRecordOptions): Promise { + + + /** + * 删除dns解析记录,清理申请痕迹 + * @param options + */ + async removeRecord(options: RemoveRecordOptions): Promise { const { fullRecord, value, record } = options; - this.logger.info('删除域名解析:', fullRecord, value, record); - //TODO 这里调用删除txt dns解析记录接口 - const access = this.access; - this.logger.debug('access', access); - this.logger.info('删除域名解析成功:', fullRecord, value); + this.logger.info('删除域名解析:', fullRecord, value); + if(!record){ + this.logger.info('record不存在'); + return + } + //这里调用删除txt dns解析记录接口 + const zoneId = record.zone_id; + const recordId = record.id; + let url = `https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records/${recordId}`; + await this.doRequestApi(url,null,"delete") + this.logger.info(`删除域名解析成功:fullRecord=${fullRecord},value=${value}`); } } -//TODO 实例化这个provider,将其自动注册到系统中 +//实例化这个provider,将其自动注册到系统中 new CloudflareDnsProvider(); diff --git a/packages/ui/certd-server/src/plugins/plugin-demo/access.ts b/packages/ui/certd-server/src/plugins/plugin-demo/access.ts index cbd714ed5..3ccf5f456 100644 --- a/packages/ui/certd-server/src/plugins/plugin-demo/access.ts +++ b/packages/ui/certd-server/src/plugins/plugin-demo/access.ts @@ -1,4 +1,4 @@ -import { IsAccess, AccessInput } from '@certd/pipeline'; +import { IsAccess, AccessInput, IAccess } from "@certd/pipeline"; /** * 这个注解将注册一个授权配置 @@ -9,7 +9,7 @@ import { IsAccess, AccessInput } from '@certd/pipeline'; title: '授权插件示例', desc: '', }) -export class DemoAccess { +export class DemoAccess implements IAccess{ /** * 授权属性配置 */ diff --git a/packages/ui/certd-server/src/plugins/plugin-demo/dns-provider.ts b/packages/ui/certd-server/src/plugins/plugin-demo/dns-provider.ts index 71d06fac0..734f242c6 100644 --- a/packages/ui/certd-server/src/plugins/plugin-demo/dns-provider.ts +++ b/packages/ui/certd-server/src/plugins/plugin-demo/dns-provider.ts @@ -1,43 +1,81 @@ import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert"; -import { Autowire, ILogger } from "@certd/pipeline"; +import { Autowire, HttpClient, ILogger } from "@certd/pipeline"; import { DemoAccess } from "./access"; -// TODO 这里注册一个dnsProvider + +type DemoRecord = { + // 这里定义Record记录的数据结构,跟对应云平台接口返回值一样即可,一般是拿到id就行,用于删除txt解析记录,清理申请痕迹 + // id:string +} + +// 这里通过IsDnsProvider注册一个dnsProvider @IsDnsProvider({ - name: 'demo', - title: 'Dns提供商Demo', - desc: 'dns provider示例', - accessType: 'demo', //这里是对应的access name + name: "demo", + title: "Dns提供商Demo", + desc: "dns provider示例", + // 这里是对应的云平台的access类型名称 + accessType: "demo" }) -export class DemoDnsProvider extends AbstractDnsProvider { +export class DemoDnsProvider extends AbstractDnsProvider { + // 通过Autowire注入工具对象 @Autowire() access!: DemoAccess; @Autowire() logger!: ILogger; + http!: HttpClient; async onInstance() { - const access: any = this.access; - this.logger.debug('access', access); + // 也可以通过ctx成员变量传递context, 与Autowire效果一样 + this.http = this.ctx.http; + this.logger.debug("access", this.access); //初始化的操作 //... } + /** + * 创建dns解析记录,用于验证域名所有权 + */ async createRecord(options: CreateRecordOptions): Promise { - const { fullRecord, value, type,domain } = options; - this.logger.info('添加域名解析:', fullRecord, value, type,domain); - //TODO 然后调用接口,创建txt类型的dns解析记录 - // .. 这里调用对应平台的后台接口 - const access = this.access; - this.logger.debug('access', access); + /** + * options 参数说明 + * fullRecord: '_acme-challenge.example.com', + * value: 一串uuid + * type: 'TXT', + * domain: 'example.com' + */ + const { fullRecord, value, type, domain } = options; + this.logger.info("添加域名解析:", fullRecord, value, type, domain); + + // 调用创建dns解析记录的对应的云端接口,创建txt类型的dns解析记录 + // 请根据实际接口情况调用,例如: + // const createDnsRecordUrl = "xxx" + // const record = this.http.post(createDnsRecordUrl,{ + // // 授权参数 + // // 创建dns解析记录的参数 + // }) + // //返回本次创建的dns解析记录,这个记录会在删除的时候用到 + // return record } - async removeRecord(options: RemoveRecordOptions): Promise { + + /** + * 删除dns解析记录,清理申请痕迹 + * @param options + */ + async removeRecord(options: RemoveRecordOptions): Promise { const { fullRecord, value, record } = options; - this.logger.info('删除域名解析:', fullRecord, value, record); - //TODO 这里调用删除txt dns解析记录接口 - const access = this.access; - this.logger.debug('access', access); - this.logger.info('删除域名解析成功:', fullRecord, value); + this.logger.info("删除域名解析:", fullRecord, value, record); + //这里调用删除txt dns解析记录接口 + //请根据实际接口情况调用,例如: + + // const deleteDnsRecordUrl = "xxx" + // const res = this.http.delete(deleteDnsRecordUrl,{ + // // 授权参数 + // // 删除dns解析记录的参数 + // }) + // + + this.logger.info("删除域名解析成功:", fullRecord, value); } } diff --git a/packages/ui/certd-server/src/plugins/plugin-huawei/dns-provider/huawei-dns-provider.ts b/packages/ui/certd-server/src/plugins/plugin-huawei/dns-provider/huawei-dns-provider.ts index 5068ece48..43ea9b10c 100644 --- a/packages/ui/certd-server/src/plugins/plugin-huawei/dns-provider/huawei-dns-provider.ts +++ b/packages/ui/certd-server/src/plugins/plugin-huawei/dns-provider/huawei-dns-provider.ts @@ -100,7 +100,7 @@ export class HuaweiDnsProvider extends AbstractDnsProvider{ throw e; } } - async removeRecord(options: RemoveRecordOptions): Promise { + async removeRecord(options: RemoveRecordOptions): Promise { const { fullRecord, value, record } = options; const req: ApiRequestOptions = { url: `${this.dnsEndpoint}/v2/zones/${record.zone_id}/recordsets/${record.id}`, diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/dns-provider/dnspod-dns-provider.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/dns-provider/dnspod-dns-provider.ts index 2c85d9d07..540b2b961 100644 --- a/packages/ui/certd-server/src/plugins/plugin-tencent/dns-provider/dnspod-dns-provider.ts +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/dns-provider/dnspod-dns-provider.ts @@ -91,7 +91,7 @@ export class DnspodDnsProvider extends AbstractDnsProvider { return ret.record; } - async removeRecord(options: RemoveRecordOptions) { + async removeRecord(options: RemoveRecordOptions) { const { fullRecord, value, record } = options; const domain = await this.matchDomain(fullRecord);