From eade2c2b681569f03e9cd466e7d5bcd6703ed492 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Thu, 4 Jul 2024 01:14:09 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81zero=20ssl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/acme-client/src/auto.js | 3 ++ packages/core/acme-client/src/index.js | 1 + packages/core/acme-client/types/index.d.ts | 1 + .../plugin-cert/src/access/eab-access.ts | 29 ++++++++++++++++ .../plugins/plugin-cert/src/access/index.ts | 1 + packages/plugins/plugin-cert/src/index.ts | 1 + .../src/plugin/cert-plugin/acme.ts | 22 +++++++++--- .../src/plugin/cert-plugin/index.ts | 34 +++++++++++++++++-- 8 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 packages/plugins/plugin-cert/src/access/eab-access.ts create mode 100644 packages/plugins/plugin-cert/src/access/index.ts diff --git a/packages/core/acme-client/src/auto.js b/packages/core/acme-client/src/auto.js index 21adb089..10e81fe5 100644 --- a/packages/core/acme-client/src/auto.js +++ b/packages/core/acme-client/src/auto.js @@ -36,6 +36,9 @@ module.exports = async (client, userOpts) => { if (opts.email) { accountPayload.contact = [`mailto:${opts.email}`]; } + if (opts.externalAccountBinding) { + accountPayload.externalAccountBinding = opts.externalAccountBinding; + } /** * Register account diff --git a/packages/core/acme-client/src/index.js b/packages/core/acme-client/src/index.js index 209cf1da..9550e87c 100644 --- a/packages/core/acme-client/src/index.js +++ b/packages/core/acme-client/src/index.js @@ -18,6 +18,7 @@ exports.directory = { production: 'https://acme-v02.api.letsencrypt.org/directory', }, zerossl: { + staging: 'https://acme.zerossl.com/v2/DV90', production: 'https://acme.zerossl.com/v2/DV90', }, }; diff --git a/packages/core/acme-client/types/index.d.ts b/packages/core/acme-client/types/index.d.ts index 13455c3d..341bac7f 100644 --- a/packages/core/acme-client/types/index.d.ts +++ b/packages/core/acme-client/types/index.d.ts @@ -92,6 +92,7 @@ export const directory: { production: string }, zerossl: { + staging: string, production: string } }; diff --git a/packages/plugins/plugin-cert/src/access/eab-access.ts b/packages/plugins/plugin-cert/src/access/eab-access.ts new file mode 100644 index 00000000..d1005ca3 --- /dev/null +++ b/packages/plugins/plugin-cert/src/access/eab-access.ts @@ -0,0 +1,29 @@ +import { IsAccess, AccessInput } from "@certd/pipeline"; + +@IsAccess({ + name: "eab", + title: "EABAccess", + desc: "ZeroSSL证书申请需要EAB授权", +}) +export class EabAccess { + @AccessInput({ + title: "KID", + component: { + placeholder: "kid", + }, + helper: "EAB KID", + required: true, + }) + kid = ""; + @AccessInput({ + title: "HMACKey", + component: { + placeholder: "HMAC Key", + }, + helper: "EAB HMAC Key", + required: true, + }) + hmacKey = ""; +} + +new EabAccess(); diff --git a/packages/plugins/plugin-cert/src/access/index.ts b/packages/plugins/plugin-cert/src/access/index.ts new file mode 100644 index 00000000..927012ad --- /dev/null +++ b/packages/plugins/plugin-cert/src/access/index.ts @@ -0,0 +1 @@ +export * from "./eab-access"; diff --git a/packages/plugins/plugin-cert/src/index.ts b/packages/plugins/plugin-cert/src/index.ts index 6765bc10..a8a6c9d7 100644 --- a/packages/plugins/plugin-cert/src/index.ts +++ b/packages/plugins/plugin-cert/src/index.ts @@ -1,2 +1,3 @@ export * from "./plugin"; export * from "./dns-provider"; +export * from "./access"; 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 22ff80b1..243561d6 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts @@ -6,18 +6,24 @@ import { Logger } from "log4js"; import { IContext } from "@certd/pipeline"; import { IDnsProvider } from "../../dns-provider"; import psl from "psl"; +import { ClientExternalAccountBindingOptions } from "@certd/acme-client"; export type CertInfo = { crt: string; key: string; csr: string; }; +export type SSLProvider = "letsencrypt" | "buypass" | "zerossl"; export class AcmeService { userContext: IContext; logger: Logger; - constructor(options: { userContext: IContext; logger: Logger }) { + sslProvider: SSLProvider; + eab?: ClientExternalAccountBindingOptions; + constructor(options: { userContext: IContext; logger: Logger; sslProvider: SSLProvider; eab?: ClientExternalAccountBindingOptions }) { this.userContext = options.userContext; this.logger = options.logger; + this.sslProvider = options.sslProvider || "letsencrypt"; + this.eab = options.eab; acme.setLogger((text: string) => { this.logger.info(text); }); @@ -28,7 +34,7 @@ export class AcmeService { } buildAccountKey(email: string) { - return "acme.config." + email; + return `acme.config.${this.sslProvider}.${email}`; } async saveAccountConfig(email: string, conf: any) { @@ -41,11 +47,18 @@ export class AcmeService { conf.key = await this.createNewKey(); await this.saveAccountConfig(email, conf); } + let directoryUrl = ""; + if (isTest) { + directoryUrl = acme.directory[this.sslProvider].staging; + } else { + directoryUrl = acme.directory[this.sslProvider].production; + } const client = new acme.Client({ - directoryUrl: isTest ? acme.directory.letsencrypt.staging : acme.directory.letsencrypt.production, + directoryUrl: directoryUrl, accountKey: conf.key, accountUrl: conf.accountUrl, - backoffAttempts: 70, + externalAccountBinding: this.eab, + backoffAttempts: 30, backoffMin: 5000, backoffMax: 10000, }); @@ -54,6 +67,7 @@ export class AcmeService { const accountPayload = { termsOfServiceAgreed: true, contact: [`mailto:${email}`], + externalAccountBinding: this.eab, }; await client.createAccount(accountPayload); conf.accountUrl = client.getAccountUrl(); 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 58595f53..542093c9 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts @@ -1,6 +1,6 @@ import { AbstractTaskPlugin, Decorator, HttpClient, IAccessService, IContext, IsTaskPlugin, RunStrategy, Step, TaskInput, TaskOutput } from "@certd/pipeline"; import dayjs from "dayjs"; -import { AcmeService, CertInfo } from "./acme"; +import { AcmeService, CertInfo, SSLProvider } from "./acme"; import _ from "lodash"; import { Logger } from "log4js"; import { DnsProviderContext, DnsProviderDefine, dnsProviderRegistry } from "../../dns-provider"; @@ -56,6 +56,32 @@ export class CertApplyPlugin extends AbstractTaskPlugin { }) email!: string; + @TaskInput({ + title: "证书提供商", + value: "letsencrypt", + component: { + name: "a-select", + vModel: "value", + options: [ + { value: "letsencrypt", label: "Let's Encrypt" }, + // { value: "buypass", label: "Buypass" }, + { value: "zerossl", label: "ZeroSSL" }, + ], + }, + required: true, + }) + sslProvider!: SSLProvider; + + @TaskInput({ + title: "EAB授权", + component: { + name: "pi-access-selector", + type: "eab", + }, + helper: "如果使用ZeroSSL证书,需要提供EAB授权, 请前往 https://app.zerossl.com/developer 生成 'EAB Credentials for ACME Clients' ", + }) + eabAccessId!: number; + @TaskInput({ title: "DNS提供商", component: { @@ -135,7 +161,11 @@ export class CertApplyPlugin extends AbstractTaskPlugin { this.http = this.ctx.http; this.lastStatus = this.ctx.lastStatus as Step; - this.acme = new AcmeService({ userContext: this.userContext, logger: this.logger }); + let eab: any = null; + if (this.eabAccessId) { + eab = await this.ctx.accessService.getById(this.eabAccessId); + } + this.acme = new AcmeService({ userContext: this.userContext, logger: this.logger, sslProvider: this.sslProvider, eab }); } async execute(): Promise {