From 54db74428259de64d12230c2ab7353ae11197bbc Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Fri, 3 Jan 2025 01:17:20 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96acme=20sdk?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/acme-client/src/auto.js | 29 +--- packages/core/acme-client/src/error.js | 2 + packages/core/acme-client/types/index.d.ts | 6 +- .../src/plugin/cert-plugin/acme.ts | 133 +++++++++--------- packages/plugins/plugin-lib/package.json | 1 + .../plugin-lib/src/aliyun/lib/oss-client.ts | 20 ++- 6 files changed, 94 insertions(+), 97 deletions(-) diff --git a/packages/core/acme-client/src/auto.js b/packages/core/acme-client/src/auto.js index 06b49277..a10afc61 100644 --- a/packages/core/acme-client/src/auto.js +++ b/packages/core/acme-client/src/auto.js @@ -99,31 +99,14 @@ export default async (client, userOpts) => { return; } + const keyAuthorizationGetter = async (challenge) => { + return await client.getChallengeKeyAuthorization(challenge); + } + try { - /* Select challenge based on priority */ - const challenge = authz.challenges.sort((a, b) => { - const aidx = opts.challengePriority.indexOf(a.type); - const bidx = opts.challengePriority.indexOf(b.type); - - if (aidx === -1) return 1; - if (bidx === -1) return -1; - return aidx - bidx; - }).slice(0, 1)[0]; - - if (!challenge) { - throw new Error(`Unable to select challenge for ${d}, no challenge found`); - } - - log(`[auto] [${d}] Found ${authz.challenges.length} challenges, selected type: ${challenge.type}`); - - /* Trigger challengeCreateFn() */ log(`[auto] [${d}] Trigger challengeCreateFn()`); - const keyAuthorization = await client.getChallengeKeyAuthorization(challenge); - try { - const { recordReq, recordRes, dnsProvider } = await opts.challengeCreateFn(authz, challenge, keyAuthorization); - log(`[auto] [${d}] challengeCreateFn success`); - log(`[auto] [${d}] add challengeRemoveFn()`); + const { recordReq, recordRes, dnsProvider,challenge ,keyAuthorization} = await opts.challengeCreateFn(authz, keyAuthorizationGetter); clearTasks.push(async () => { /* Trigger challengeRemoveFn(), suppress errors */ log(`[auto] [${d}] Trigger challengeRemoveFn()`); @@ -141,7 +124,7 @@ export default async (client, userOpts) => { await wait(60 * 1000); } else { - log(`[auto] [${d}] Running challenge verification`); + log(`[auto] [${d}] Running challenge verification, type = ${challenge.type}`); try { await client.verifyChallenge(authz, challenge); } diff --git a/packages/core/acme-client/src/error.js b/packages/core/acme-client/src/error.js index 4539457e..2595aa05 100644 --- a/packages/core/acme-client/src/error.js +++ b/packages/core/acme-client/src/error.js @@ -5,3 +5,5 @@ export class CancelError extends Error { } } + + diff --git a/packages/core/acme-client/types/index.d.ts b/packages/core/acme-client/types/index.d.ts index e53aabbc..09179f21 100644 --- a/packages/core/acme-client/types/index.d.ts +++ b/packages/core/acme-client/types/index.d.ts @@ -4,6 +4,8 @@ import { AxiosInstance } from 'axios'; import * as rfc8555 from './rfc8555'; +import {CancelError} from '../src/error.js' +export * from '../src/error.js' export type PrivateKeyBuffer = Buffer; export type PublicKeyBuffer = Buffer; @@ -56,7 +58,7 @@ export interface ClientExternalAccountBindingOptions { export interface ClientAutoOptions { csr: CsrBuffer | CsrString; - challengeCreateFn: (authz: Authorization, challenge: rfc8555.Challenge, keyAuthorization: string) => Promise<{recordReq:any,recordRes:any,dnsProvider:any}>; + challengeCreateFn: (authz: Authorization, keyAuthorization: (challenge:rfc8555.Challenge)=>Promise) => Promise<{recordReq?:any,recordRes?:any,dnsProvider?:any,challenge: rfc8555.Challenge,keyAuthorization:string}>; challengeRemoveFn: (authz: Authorization, challenge: rfc8555.Challenge, keyAuthorization: string,recordReq:any, recordRes:any,dnsProvider:any) => Promise; email?: string; termsOfServiceAgreed?: boolean; @@ -202,4 +204,4 @@ export function setLogger(fn: (message: any, ...args: any[]) => void): void; export function walkTxtRecord(record: any): Promise; -export const CancelError: Error; +export const CancelError: typeof CancelError; \ No newline at end of file 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 fa496832..24a6cd21 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts @@ -7,7 +7,6 @@ import { IContext } from "@certd/pipeline"; import { ILogger, utils } from "@certd/basic"; import { IDnsProvider, parseDomain } from "../../dns-provider/index.js"; import { HttpChallengeUploader } from "./uploads/api.js"; - export type CnameVerifyPlan = { type?: string; domain: string; @@ -170,75 +169,36 @@ export class AcmeService { return key.toString(); } - async challengeCreateFn(authz: any, challenge: any, keyAuthorization: string, providers: Providers) { + async challengeCreateFn(authz: any, keyAuthorizationGetter: (challenge: Challenge) => Promise, providers: Providers) { this.logger.info("Triggered challengeCreateFn()"); - /* http-01 */ const fullDomain = authz.identifier.value; - if (challenge.type === "http-01") { + let domain = parseDomain(fullDomain); + this.logger.info("主域名为:" + domain); + + const getChallenge = (type: string) => { + return authz.challenges.find((c: any) => c.type === type); + }; + + const doHttpVerify = async (challenge: any, httpUploader: HttpChallengeUploader) => { + const keyAuthorization = await keyAuthorizationGetter(challenge); + this.logger.info("http校验"); const filePath = `.well-known/acme-challenge/${challenge.token}`; const fileContents = keyAuthorization; this.logger.info(`校验 ${fullDomain} ,准备上传文件:${filePath}`); - - let httpUploaderPlan: HttpVerifyPlan = null; - if (providers.domainsVerifyPlan) { - //查找文件上传配置 - for (const mainDomain in providers.domainsVerifyPlan) { - const domainVerifyPlan = providers.domainsVerifyPlan[mainDomain]; - if (domainVerifyPlan && domainVerifyPlan.type === "http" && domainVerifyPlan.httpVerifyPlan[fullDomain]) { - httpUploaderPlan = domainVerifyPlan.httpVerifyPlan[fullDomain]; - break; - } - } - } - if (httpUploaderPlan == null) { - throw new Error(`未找到域名【${fullDomain}】的http校验计划`); - } - - await httpUploaderPlan.httpUploader.upload(filePath, fileContents); + await httpUploader.upload(filePath, fileContents); this.logger.info(`上传文件【${filePath}】成功`); - } else if (challenge.type === "dns-01") { - /* dns-01 */ - let fullRecord = `_acme-challenge.${fullDomain}`; + return { + challenge, + keyAuthorization, + }; + }; + + const doDnsVerify = async (challenge: any, fullRecord: string, dnsProvider: IDnsProvider) => { + this.logger.info("dns校验"); + const keyAuthorization = await keyAuthorizationGetter(challenge); + const recordValue = keyAuthorization; - - this.logger.info(`Creating TXT record for ${fullDomain}: ${fullRecord}`); - /* Replace this */ - this.logger.info(`Would create TXT record "${fullRecord}" with value "${recordValue}"`); - - let domain = parseDomain(fullDomain); - this.logger.info("解析到域名domain=" + domain); - - let dnsProvider = providers.dnsProvider; - if (providers.domainsVerifyPlan) { - //按照计划执行 - const domainVerifyPlan = providers.domainsVerifyPlan[domain]; - if (domainVerifyPlan) { - if (domainVerifyPlan.type === "dns") { - dnsProvider = domainVerifyPlan.dnsProvider; - } else if (domainVerifyPlan.type === "cname") { - const cnameVerifyPlan = domainVerifyPlan.cnameVerifyPlan; - if (cnameVerifyPlan) { - const cname = cnameVerifyPlan[fullDomain]; - if (cname) { - dnsProvider = cname.dnsProvider; - domain = parseDomain(cname.domain); - fullRecord = cname.fullRecord; - } - } else { - this.logger.error("未找到域名Cname校验计划,使用默认的dnsProvider"); - } - } else if (domainVerifyPlan.type === "http") { - throw new Error("切换为http校验"); - } else { - // this.logger.error("不支持的校验类型", domainVerifyPlan.type); - throw new Error("不支持的校验类型", domainVerifyPlan.type); - } - } else { - this.logger.info("未找到域名校验计划,使用默认的dnsProvider"); - } - } - let hostRecord = fullRecord.replace(`${domain}`, ""); if (hostRecord.endsWith(".")) { hostRecord = hostRecord.substring(0, hostRecord.length - 1); @@ -258,8 +218,50 @@ export class AcmeService { recordReq, recordRes, dnsProvider, + challenge, + keyAuthorization, }; + }; + + let dnsProvider = providers.dnsProvider; + let fullRecord = `_acme-challenge.${fullDomain}`; + + if (providers.domainsVerifyPlan) { + //按照计划执行 + const domainVerifyPlan = providers.domainsVerifyPlan[domain]; + if (domainVerifyPlan) { + if (domainVerifyPlan.type === "dns") { + dnsProvider = domainVerifyPlan.dnsProvider; + } else if (domainVerifyPlan.type === "cname") { + const cnameVerifyPlan = domainVerifyPlan.cnameVerifyPlan; + if (cnameVerifyPlan) { + const cname = cnameVerifyPlan[fullDomain]; + if (cname) { + dnsProvider = cname.dnsProvider; + domain = parseDomain(cname.domain); + fullRecord = cname.fullRecord; + } + } else { + this.logger.error("未找到域名Cname校验计划,使用默认的dnsProvider"); + } + } else if (domainVerifyPlan.type === "http") { + const httpVerifyPlan = domainVerifyPlan.httpVerifyPlan; + if (httpVerifyPlan) { + const httpChallenge = getChallenge("http-01"); + return await doHttpVerify(httpChallenge, httpVerifyPlan[fullDomain].httpUploader); + } else { + throw new Error("未找到域名【" + fullDomain + "】的http校验配置"); + } + } else { + throw new Error("不支持的校验类型", domainVerifyPlan.type); + } + } else { + this.logger.info("未找到域名校验计划,使用默认的dnsProvider"); + } } + + const dnsChallenge = getChallenge("dns-01"); + return await doDnsVerify(dnsChallenge, fullRecord, dnsProvider); } /** @@ -376,10 +378,9 @@ export class AcmeService { challengePriority: ["dns-01", "http-01"], challengeCreateFn: async ( authz: acme.Authorization, - challenge: Challenge, - keyAuthorization: string - ): Promise<{ recordReq: any; recordRes: any; dnsProvider: any }> => { - return await this.challengeCreateFn(authz, challenge, keyAuthorization, providers); + keyAuthorizationGetter: (challenge: Challenge) => Promise + ): Promise<{ recordReq?: any; recordRes?: any; dnsProvider?: any; challenge: Challenge; keyAuthorization: string }> => { + return await this.challengeCreateFn(authz, keyAuthorizationGetter, providers); }, challengeRemoveFn: async ( authz: acme.Authorization, diff --git a/packages/plugins/plugin-lib/package.json b/packages/plugins/plugin-lib/package.json index 779f36e9..a42530f6 100644 --- a/packages/plugins/plugin-lib/package.json +++ b/packages/plugins/plugin-lib/package.json @@ -19,6 +19,7 @@ "@certd/basic": "^1.29.2", "@certd/pipeline": "^1.29.2", "@kubernetes/client-node": "0.21.0", + "ali-oss": "^6.21.0", "basic-ftp": "^5.0.5", "dayjs": "^1.11.7", "iconv-lite": "^0.6.3", diff --git a/packages/plugins/plugin-lib/src/aliyun/lib/oss-client.ts b/packages/plugins/plugin-lib/src/aliyun/lib/oss-client.ts index ee812ad4..0016ce0a 100644 --- a/packages/plugins/plugin-lib/src/aliyun/lib/oss-client.ts +++ b/packages/plugins/plugin-lib/src/aliyun/lib/oss-client.ts @@ -13,9 +13,12 @@ export class AliossClient { } async init() { + if (this.client) { + return; + } // @ts-ignore const OSS = await import("ali-oss"); - this.client = new OSS.default({ + const ossClient = new OSS.default({ accessKeyId: this.access.accessKeyId, accessKeySecret: this.access.accessKeySecret, // yourRegion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。 @@ -25,16 +28,20 @@ export class AliossClient { // yourBucketName填写Bucket名称。 bucket: this.bucket, }); + // oss + + this.client = ossClient; } - async doRequest(client: any, bucket: string, xml: string, params: any) { - params = client._bucketRequestParams("POST", bucket, { + async doRequest(bucket: string, xml: string, params: any) { + await this.init(); + params = this.client._bucketRequestParams("POST", bucket, { ...params, }); params.content = xml; params.mime = "xml"; params.successStatuses = [200]; - const res = await client.request(params); + const res = await this.client.request(params); this.checkRet(res); return res; } @@ -46,11 +53,12 @@ export class AliossClient { } async uploadFile(filePath: string, content: Buffer) { - const memFile = new File([content], filePath); - return await this.client.put(filePath, memFile); + await this.init(); + return await this.client.put(filePath, content); } async removeFile(filePath: string) { + await this.init(); return await this.client.delete(filePath); } }