2024-07-21 02:26:03 +08:00
|
|
|
|
import { Decorator, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
|
2024-08-23 17:41:02 +08:00
|
|
|
|
import type { CertInfo, PrivateKeyType, SSLProvider } from "./acme.js";
|
2024-07-18 21:10:13 +08:00
|
|
|
|
import { AcmeService } from "./acme.js";
|
2024-07-15 00:30:33 +08:00
|
|
|
|
import _ from "lodash-es";
|
|
|
|
|
|
import { DnsProviderContext, DnsProviderDefine, dnsProviderRegistry } from "../../dns-provider/index.js";
|
|
|
|
|
|
import { CertReader } from "./cert-reader.js";
|
2024-07-18 21:10:13 +08:00
|
|
|
|
import { CertApplyBasePlugin } from "./base.js";
|
2024-05-30 10:54:18 +08:00
|
|
|
|
|
2023-05-23 18:01:20 +08:00
|
|
|
|
export { CertReader };
|
|
|
|
|
|
export type { CertInfo };
|
2023-01-11 20:39:48 +08:00
|
|
|
|
|
2022-12-29 23:52:51 +08:00
|
|
|
|
@IsTaskPlugin({
|
|
|
|
|
|
name: "CertApply",
|
2024-07-21 02:26:03 +08:00
|
|
|
|
title: "证书申请(JS版)",
|
|
|
|
|
|
group: pluginGroups.cert.key,
|
2022-12-29 23:52:51 +08:00
|
|
|
|
desc: "免费通配符域名证书申请,支持多个域名打到同一个证书上",
|
|
|
|
|
|
default: {
|
2022-11-08 22:10:42 +08:00
|
|
|
|
input: {
|
2022-12-29 23:52:51 +08:00
|
|
|
|
renewDays: 20,
|
|
|
|
|
|
forceUpdate: false,
|
2022-11-08 22:10:42 +08:00
|
|
|
|
},
|
2022-12-29 23:52:51 +08:00
|
|
|
|
strategy: {
|
|
|
|
|
|
runStrategy: RunStrategy.AlwaysRun,
|
2022-11-08 22:10:42 +08:00
|
|
|
|
},
|
2022-12-29 23:52:51 +08:00
|
|
|
|
},
|
2022-11-08 22:10:42 +08:00
|
|
|
|
})
|
2024-07-18 21:10:13 +08:00
|
|
|
|
export class CertApplyPlugin extends CertApplyBasePlugin {
|
2024-07-04 01:14:09 +08:00
|
|
|
|
@TaskInput({
|
|
|
|
|
|
title: "证书提供商",
|
2024-08-06 11:23:23 +08:00
|
|
|
|
value: "letsencrypt",
|
2024-07-04 01:14:09 +08:00
|
|
|
|
component: {
|
|
|
|
|
|
name: "a-select",
|
|
|
|
|
|
vModel: "value",
|
|
|
|
|
|
options: [
|
|
|
|
|
|
{ value: "letsencrypt", label: "Let's Encrypt" },
|
2024-08-23 13:15:06 +08:00
|
|
|
|
{ value: "google", label: "Google" },
|
2024-07-04 01:14:09 +08:00
|
|
|
|
{ value: "zerossl", label: "ZeroSSL" },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
2024-08-25 03:14:07 +08:00
|
|
|
|
helper: "如果letsencrypt.org或dv.acme-v02.api.pki.goog无法访问,请尝试开启代理选项\n如果使用ZeroSSL、google证书,需要提供EAB授权",
|
2024-07-04 01:14:09 +08:00
|
|
|
|
required: true,
|
|
|
|
|
|
})
|
|
|
|
|
|
sslProvider!: SSLProvider;
|
|
|
|
|
|
|
2024-08-23 17:41:02 +08:00
|
|
|
|
@TaskInput({
|
2024-08-25 11:57:07 +08:00
|
|
|
|
title: "加密算法",
|
2024-08-25 11:56:15 +08:00
|
|
|
|
value: "rsa_2048",
|
2024-08-23 17:41:02 +08:00
|
|
|
|
component: {
|
|
|
|
|
|
name: "a-select",
|
|
|
|
|
|
vModel: "value",
|
|
|
|
|
|
options: [
|
2024-08-25 11:56:15 +08:00
|
|
|
|
{ value: "rsa_1024", label: "RSA 1024" },
|
|
|
|
|
|
{ value: "rsa_2048", label: "RSA 2048" },
|
|
|
|
|
|
{ value: "rsa_3072", label: "RSA 3072" },
|
|
|
|
|
|
{ value: "rsa_4096", label: "RSA 4096" },
|
|
|
|
|
|
{ value: "ec_256", label: "EC 256" },
|
|
|
|
|
|
{ value: "ec_384", label: "EC 384" },
|
2024-08-25 12:07:47 +08:00
|
|
|
|
// { value: "ec_521", label: "EC 521" },
|
2024-08-23 17:41:02 +08:00
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
required: true,
|
|
|
|
|
|
})
|
|
|
|
|
|
privateKeyType!: PrivateKeyType;
|
|
|
|
|
|
|
2024-07-04 01:14:09 +08:00
|
|
|
|
@TaskInput({
|
|
|
|
|
|
title: "EAB授权",
|
|
|
|
|
|
component: {
|
|
|
|
|
|
name: "pi-access-selector",
|
|
|
|
|
|
type: "eab",
|
|
|
|
|
|
},
|
2024-07-21 02:26:03 +08:00
|
|
|
|
maybeNeed: true,
|
2024-08-25 03:27:38 +08:00
|
|
|
|
helper:
|
|
|
|
|
|
"如果使用ZeroSSL或者google证书,需要提供EAB授权\nZeroSSL:请前往 https://app.zerossl.com/developer 生成 'EAB Credentials' \n Google:请前往https://github.com/certd/certd/blob/v2/doc/google/google.md",
|
2024-07-04 01:14:09 +08:00
|
|
|
|
})
|
|
|
|
|
|
eabAccessId!: number;
|
|
|
|
|
|
|
2022-12-29 23:52:51 +08:00
|
|
|
|
@TaskInput({
|
|
|
|
|
|
title: "DNS提供商",
|
|
|
|
|
|
component: {
|
|
|
|
|
|
name: "pi-dns-provider-selector",
|
|
|
|
|
|
},
|
|
|
|
|
|
required: true,
|
|
|
|
|
|
helper: "请选择dns解析提供商",
|
|
|
|
|
|
})
|
|
|
|
|
|
dnsProviderType!: string;
|
|
|
|
|
|
|
|
|
|
|
|
@TaskInput({
|
|
|
|
|
|
title: "DNS解析授权",
|
|
|
|
|
|
component: {
|
|
|
|
|
|
name: "pi-access-selector",
|
|
|
|
|
|
},
|
|
|
|
|
|
required: true,
|
|
|
|
|
|
helper: "请选择dns解析提供商授权",
|
2023-06-25 16:25:23 +08:00
|
|
|
|
reference: [
|
|
|
|
|
|
{
|
|
|
|
|
|
src: "form.dnsProviderType",
|
|
|
|
|
|
dest: "component.type",
|
|
|
|
|
|
type: "computed",
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
2022-12-29 23:52:51 +08:00
|
|
|
|
})
|
|
|
|
|
|
dnsProviderAccess!: string;
|
|
|
|
|
|
|
2024-07-25 10:38:45 +08:00
|
|
|
|
@TaskInput({
|
|
|
|
|
|
title: "使用代理",
|
2024-08-06 11:23:23 +08:00
|
|
|
|
value: false,
|
2024-07-25 10:38:45 +08:00
|
|
|
|
component: {
|
|
|
|
|
|
name: "a-switch",
|
|
|
|
|
|
vModel: "checked",
|
|
|
|
|
|
},
|
2024-08-25 03:14:07 +08:00
|
|
|
|
maybeNeed: true,
|
2024-08-25 03:27:38 +08:00
|
|
|
|
helper: "如果acme-v02.api.letsencrypt.org或dv.acme-v02.api.pki.goog被墙无法访问,请尝试开启此选项",
|
2024-07-25 10:38:45 +08:00
|
|
|
|
})
|
|
|
|
|
|
useProxy = false;
|
|
|
|
|
|
|
2024-07-08 15:35:58 +08:00
|
|
|
|
@TaskInput({
|
2024-07-08 15:47:36 +08:00
|
|
|
|
title: "跳过本地校验DNS",
|
2024-08-06 11:23:23 +08:00
|
|
|
|
value: false,
|
2024-07-08 15:35:58 +08:00
|
|
|
|
component: {
|
|
|
|
|
|
name: "a-switch",
|
|
|
|
|
|
vModel: "checked",
|
|
|
|
|
|
},
|
|
|
|
|
|
helper: "如果重试多次出现Authorization not found TXT record,导致无法申请成功,请尝试开启此选项",
|
|
|
|
|
|
})
|
|
|
|
|
|
skipLocalVerify = false;
|
|
|
|
|
|
|
2023-06-25 23:25:56 +08:00
|
|
|
|
acme!: AcmeService;
|
|
|
|
|
|
|
2024-07-18 21:10:13 +08:00
|
|
|
|
async onInit() {
|
2024-07-04 01:14:09 +08:00
|
|
|
|
let eab: any = null;
|
|
|
|
|
|
if (this.eabAccessId) {
|
|
|
|
|
|
eab = await this.ctx.accessService.getById(this.eabAccessId);
|
|
|
|
|
|
}
|
2024-07-08 15:35:58 +08:00
|
|
|
|
this.acme = new AcmeService({
|
|
|
|
|
|
userContext: this.userContext,
|
|
|
|
|
|
logger: this.logger,
|
|
|
|
|
|
sslProvider: this.sslProvider,
|
|
|
|
|
|
eab,
|
|
|
|
|
|
skipLocalVerify: this.skipLocalVerify,
|
2024-07-25 10:38:45 +08:00
|
|
|
|
useMappingProxy: this.useProxy,
|
2024-08-23 17:41:02 +08:00
|
|
|
|
privateKeyType: this.privateKeyType,
|
2024-07-08 15:35:58 +08:00
|
|
|
|
});
|
2022-11-08 22:10:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-12-29 23:52:51 +08:00
|
|
|
|
async doCertApply() {
|
|
|
|
|
|
const email = this["email"];
|
|
|
|
|
|
const domains = this["domains"];
|
|
|
|
|
|
const dnsProviderType = this["dnsProviderType"];
|
|
|
|
|
|
const dnsProviderAccessId = this["dnsProviderAccess"];
|
2022-11-08 22:10:42 +08:00
|
|
|
|
const csrInfo = _.merge(
|
|
|
|
|
|
{
|
|
|
|
|
|
country: "CN",
|
|
|
|
|
|
state: "GuangDong",
|
|
|
|
|
|
locality: "ShengZhen",
|
|
|
|
|
|
organization: "CertD Org.",
|
|
|
|
|
|
organizationUnit: "IT Department",
|
|
|
|
|
|
emailAddress: email,
|
|
|
|
|
|
},
|
2024-06-15 02:17:34 +08:00
|
|
|
|
this.csrInfo ? JSON.parse(this.csrInfo) : {}
|
2022-11-08 22:10:42 +08:00
|
|
|
|
);
|
|
|
|
|
|
this.logger.info("开始申请证书,", email, domains);
|
|
|
|
|
|
|
2023-01-11 20:39:48 +08:00
|
|
|
|
const dnsProviderPlugin = dnsProviderRegistry.get(dnsProviderType);
|
|
|
|
|
|
const DnsProviderClass = dnsProviderPlugin.target;
|
|
|
|
|
|
const dnsProviderDefine = dnsProviderPlugin.define as DnsProviderDefine;
|
2022-11-08 22:10:42 +08:00
|
|
|
|
const access = await this.accessService.getById(dnsProviderAccessId);
|
2023-01-11 20:39:48 +08:00
|
|
|
|
|
2022-11-08 22:10:42 +08:00
|
|
|
|
// @ts-ignore
|
2023-01-11 20:39:48 +08:00
|
|
|
|
const dnsProvider: IDnsProvider = new DnsProviderClass();
|
2024-06-14 01:22:07 +08:00
|
|
|
|
const context: DnsProviderContext = { access, logger: this.logger, http: this.http };
|
2023-01-11 20:39:48 +08:00
|
|
|
|
Decorator.inject(dnsProviderDefine.autowire, dnsProvider, context);
|
2024-06-14 01:22:07 +08:00
|
|
|
|
dnsProvider.setCtx(context);
|
2023-05-09 10:16:49 +08:00
|
|
|
|
await dnsProvider.onInstance();
|
2022-11-08 22:10:42 +08:00
|
|
|
|
|
2024-07-18 11:17:13 +08:00
|
|
|
|
try {
|
|
|
|
|
|
const cert = await this.acme.order({
|
|
|
|
|
|
email,
|
|
|
|
|
|
domains,
|
|
|
|
|
|
dnsProvider,
|
|
|
|
|
|
csrInfo,
|
|
|
|
|
|
isTest: false,
|
2024-08-23 17:41:02 +08:00
|
|
|
|
privateKeyType: this.privateKeyType,
|
2024-07-18 11:17:13 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const certInfo = this.formatCerts(cert);
|
|
|
|
|
|
return new CertReader(certInfo);
|
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
|
const message: string = e.message;
|
|
|
|
|
|
if (message.indexOf("redundant with a wildcard domain in the same request") >= 0) {
|
|
|
|
|
|
this.logger.error(e);
|
|
|
|
|
|
throw new Error(`通配符域名已经包含了普通域名,请删除其中一个(${message})`);
|
|
|
|
|
|
}
|
|
|
|
|
|
throw e;
|
|
|
|
|
|
}
|
2022-11-08 22:10:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2023-05-09 09:56:31 +08:00
|
|
|
|
|
|
|
|
|
|
new CertApplyPlugin();
|