mirror of
https://github.com/certd/certd.git
synced 2026-05-15 20:47:31 +08:00
fix: cnameProvider域名支持设置子域名托管
This commit is contained in:
@@ -3,6 +3,7 @@ import { IAccess } from "../access/index.js";
|
|||||||
export type CnameProvider = {
|
export type CnameProvider = {
|
||||||
id: any;
|
id: any;
|
||||||
domain: string;
|
domain: string;
|
||||||
|
subdomain?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
dnsProviderType?: string;
|
dnsProviderType?: string;
|
||||||
access?: IAccess;
|
access?: IAccess;
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
export default {
|
export default {
|
||||||
cnameTitle: "CNAME Service Configuration",
|
cnameTitle: "CNAME Service Configuration",
|
||||||
cnameDescription:
|
cnameDescription:
|
||||||
"The domain name configured here serves as a proxy for verifying other domains. When other domains apply for certificates, they map to this domain via CNAME for ownership verification. The advantage is that any domain can apply for a certificate this way without providing an AccessSecret.",
|
"The domain name configured here serves as a proxy for verifying other domains. When other domains apply for certificates, they map to this domain via CNAME for ownership verification. The advantage is that any domain can apply for a certificate this way without providing an AccessSecret.",
|
||||||
cnameLinkText: "CNAME principle and usage instructions",
|
cnameLinkText: "CNAME principle and usage instructions",
|
||||||
cnameDomain: "CNAME Domain",
|
cnameDomain: "CNAME Domain",
|
||||||
cnameDomainPlaceholder: "cname.handsfree.work",
|
cnameDomainPlaceholder: "cname.handsfree.work",
|
||||||
cnameDomainHelper:
|
cnameDomainHelper:
|
||||||
"Requires a domain registered with a DNS provider on the right (or you can transfer other domain DNS servers here).\nOnce the CNAME domain is set, it cannot be changed. It is recommended to use a first-level subdomain.",
|
"Requires a domain registered with a DNS provider on the right (or you can transfer other domain DNS servers here).\nOnce the CNAME domain is set, it cannot be changed. It is recommended to use a first-level subdomain.",
|
||||||
cnameDomainPattern: "Domain name cannot contain *",
|
cnameDomainPattern: "Domain name cannot contain *",
|
||||||
|
cnameProviderSubdomain: "Delegated Subdomain",
|
||||||
|
cnameProviderSubdomainPlaceholder: "sub.example.com",
|
||||||
|
cnameProviderSubdomainHelper: "Fill this when the CNAME domain is hosted under a delegated subdomain, for example CNAME domain cname.sub.example.com and DNS zone sub.example.com.",
|
||||||
dnsProvider: "DNS Provider",
|
dnsProvider: "DNS Provider",
|
||||||
dnsProviderAuthorization: "DNS Provider Authorization",
|
dnsProviderAuthorization: "DNS Provider Authorization",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ export default {
|
|||||||
cnameDomainPlaceholder: "cname.handsfree.work",
|
cnameDomainPlaceholder: "cname.handsfree.work",
|
||||||
cnameDomainHelper: "需要一个右边DNS提供商注册的域名(也可以将其他域名的dns服务器转移到这几家来)。\nCNAME域名一旦确定不可修改,建议使用一级子域名",
|
cnameDomainHelper: "需要一个右边DNS提供商注册的域名(也可以将其他域名的dns服务器转移到这几家来)。\nCNAME域名一旦确定不可修改,建议使用一级子域名",
|
||||||
cnameDomainPattern: "域名不能使用星号",
|
cnameDomainPattern: "域名不能使用星号",
|
||||||
|
cnameProviderSubdomain: "托管子域名",
|
||||||
|
cnameProviderSubdomainPlaceholder: "sub.example.com",
|
||||||
|
cnameProviderSubdomainHelper: "当CNAME域名本身托管在子域名下时填写,例如 CNAME域名为 cname.sub.example.com,实际DNS托管域为 sub.example.com",
|
||||||
dnsProvider: "DNS提供商",
|
dnsProvider: "DNS提供商",
|
||||||
dnsProviderAuthorization: "DNS提供商授权",
|
dnsProviderAuthorization: "DNS提供商授权",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -97,6 +97,21 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||||||
width: 200,
|
width: 200,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
subdomain: {
|
||||||
|
title: t("certd.cnameProviderSubdomain"),
|
||||||
|
type: "text",
|
||||||
|
form: {
|
||||||
|
component: {
|
||||||
|
placeholder: t("certd.cnameProviderSubdomainPlaceholder"),
|
||||||
|
},
|
||||||
|
helper: t("certd.cnameProviderSubdomainHelper"),
|
||||||
|
rules: [{ pattern: /^[^*]+$/, message: t("certd.cnameDomainPattern") }],
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
width: 200,
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
dnsProviderType: {
|
dnsProviderType: {
|
||||||
title: t("certd.dnsProvider"),
|
title: t("certd.dnsProvider"),
|
||||||
type: "dict-select",
|
type: "dict-select",
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE cd_cname_provider ADD COLUMN subdomain varchar(100);
|
||||||
@@ -11,6 +11,8 @@ export class CnameProviderEntity {
|
|||||||
userId: number;
|
userId: number;
|
||||||
@Column({ comment: '域名', length: 100 })
|
@Column({ comment: '域名', length: 100 })
|
||||||
domain: string;
|
domain: string;
|
||||||
|
@Column({ comment: '子域名托管', length: 100, nullable: true })
|
||||||
|
subdomain: string;
|
||||||
@Column({ comment: 'DNS提供商类型', name: 'dns_provider_type', length: 20 })
|
@Column({ comment: 'DNS提供商类型', name: 'dns_provider_type', length: 20 })
|
||||||
dnsProviderType: string;
|
dnsProviderType: string;
|
||||||
@Column({ comment: 'DNS授权Id', name: 'access_id' })
|
@Column({ comment: 'DNS授权Id', name: 'access_id' })
|
||||||
|
|||||||
@@ -98,6 +98,18 @@ export class CnameProviderService extends BaseService<CnameProviderEntity> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getSubDomains() {
|
||||||
|
const list = await this.repository.find({
|
||||||
|
select: {
|
||||||
|
subdomain: true,
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return list.map(item => item.subdomain?.trim()).filter((item): item is string => !!item);
|
||||||
|
}
|
||||||
|
|
||||||
async list(req: ListReq): Promise<any[]> {
|
async list(req: ListReq): Promise<any[]> {
|
||||||
const list = await super.list(req);
|
const list = await super.list(req);
|
||||||
const sysPrivateSettings = await this.settingsService.getSetting<SysPrivateSettings>(SysPrivateSettings);
|
const sysPrivateSettings = await this.settingsService.getSetting<SysPrivateSettings>(SysPrivateSettings);
|
||||||
|
|||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { describe, it } from "mocha";
|
||||||
|
import { SubDomainsGetter } from "./sub-domain-getter.js";
|
||||||
|
|
||||||
|
describe("SubDomainsGetter", () => {
|
||||||
|
it("returns subdomains configured on system cname providers", async () => {
|
||||||
|
const subDomainService = {
|
||||||
|
async getListByUserId() {
|
||||||
|
return ["example.com"];
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
const domainService = {
|
||||||
|
async findOne() {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
const cnameProviderService = {
|
||||||
|
async getSubDomains() {
|
||||||
|
return ["cname-hosted.example.com"];
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const getter = new SubDomainsGetter(1, 2, subDomainService, domainService, cnameProviderService);
|
||||||
|
|
||||||
|
assert.deepEqual(await getter.getSubDomains(), ["cname-hosted.example.com", "example.com"]);
|
||||||
|
assert.equal(await getter.hasSubDomain("txt.certd.cname-hosted.example.com"), "cname-hosted.example.com");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,22 +1,30 @@
|
|||||||
import { ISubDomainsGetter } from "@certd/plugin-cert";
|
import { ISubDomainsGetter } from "@certd/plugin-cert";
|
||||||
import { SubDomainService } from "../sub-domain-service.js";
|
import { SubDomainService } from "../sub-domain-service.js";
|
||||||
import { DomainService } from "../../../cert/service/domain-service.js";
|
import { DomainService } from "../../../cert/service/domain-service.js";
|
||||||
|
import { CnameProviderService } from "../../../cname/service/cname-provider-service.js";
|
||||||
|
|
||||||
export class SubDomainsGetter implements ISubDomainsGetter {
|
export class SubDomainsGetter implements ISubDomainsGetter {
|
||||||
userId: number;
|
userId: number;
|
||||||
projectId: number;
|
projectId: number;
|
||||||
subDomainService: SubDomainService;
|
subDomainService: SubDomainService;
|
||||||
domainService: DomainService;
|
domainService: DomainService;
|
||||||
|
cnameProviderService: CnameProviderService;
|
||||||
|
|
||||||
constructor(userId: number, projectId: number, subDomainService: SubDomainService, domainService: DomainService) {
|
constructor(userId: number, projectId: number, subDomainService: SubDomainService, domainService: DomainService, cnameProviderService: CnameProviderService) {
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
this.projectId = projectId;
|
this.projectId = projectId;
|
||||||
this.subDomainService = subDomainService;
|
this.subDomainService = subDomainService;
|
||||||
this.domainService = domainService;
|
this.domainService = domainService;
|
||||||
|
this.cnameProviderService = cnameProviderService;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSubDomains() {
|
async getSubDomains() {
|
||||||
return await this.subDomainService.getListByUserId(this.userId, this.projectId)
|
const projectSubDomains = await this.subDomainService.getListByUserId(this.userId, this.projectId) || [];
|
||||||
|
const cnameProviderSubDomains = await this.cnameProviderService.getSubDomains();
|
||||||
|
return [...projectSubDomains, ...cnameProviderSubDomains]
|
||||||
|
.map(item => item?.trim())
|
||||||
|
.filter((item): item is string => !!item)
|
||||||
|
.sort((a, b) => b.length - a.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
async hasSubDomain(fullDomain: string) {
|
async hasSubDomain(fullDomain: string) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { SubDomainService } from "../sub-domain-service.js";
|
|||||||
import { CertInfoGetter } from "./cert-info-getter.js";
|
import { CertInfoGetter } from "./cert-info-getter.js";
|
||||||
import { CertInfoService } from "../../../monitor/index.js";
|
import { CertInfoService } from "../../../monitor/index.js";
|
||||||
import { ICertInfoGetter } from "@certd/plugin-lib";
|
import { ICertInfoGetter } from "@certd/plugin-lib";
|
||||||
|
import { CnameProviderService } from "../../../cname/service/cname-provider-service.js";
|
||||||
|
|
||||||
const serviceNames = [
|
const serviceNames = [
|
||||||
'ocrService',
|
'ocrService',
|
||||||
@@ -53,7 +54,8 @@ export class TaskServiceGetter implements IServiceGetter{
|
|||||||
async getSubDomainsGetter(): Promise<SubDomainsGetter> {
|
async getSubDomainsGetter(): Promise<SubDomainsGetter> {
|
||||||
const subDomainsService:SubDomainService = await this.appCtx.getAsync("subDomainService")
|
const subDomainsService:SubDomainService = await this.appCtx.getAsync("subDomainService")
|
||||||
const domainService:DomainService = await this.appCtx.getAsync("domainService")
|
const domainService:DomainService = await this.appCtx.getAsync("domainService")
|
||||||
return new SubDomainsGetter(this.userId,this.projectId, subDomainsService,domainService)
|
const cnameProviderService:CnameProviderService = await this.appCtx.getAsync("cnameProviderService")
|
||||||
|
return new SubDomainsGetter(this.userId,this.projectId, subDomainsService,domainService,cnameProviderService)
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCertInfoGetter(): Promise<ICertInfoGetter> {
|
async getCertInfoGetter(): Promise<ICertInfoGetter> {
|
||||||
@@ -102,4 +104,3 @@ export type TaskServiceCreateReq = {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -112,3 +112,65 @@ describe("AcmeService account config", () => {
|
|||||||
assert.match(error.message, /请重新获取EAB授权并刷新ACME账号私钥后重试/);
|
assert.match(error.message, /请重新获取EAB授权并刷新ACME账号私钥后重试/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("AcmeService challenge", () => {
|
||||||
|
it("parses cname TXT full record to choose the delegated DNS zone", async () => {
|
||||||
|
const parseCalls: string[] = [];
|
||||||
|
const service = new AcmeService({
|
||||||
|
userId: 1,
|
||||||
|
userContext: {} as any,
|
||||||
|
logger: logger as any,
|
||||||
|
sslProvider: "letsencrypt",
|
||||||
|
domainParser: {
|
||||||
|
async parse(fullDomain: string) {
|
||||||
|
parseCalls.push(fullDomain);
|
||||||
|
if (fullDomain === "certd-key.cname.sub.example.com") {
|
||||||
|
return "sub.example.com";
|
||||||
|
}
|
||||||
|
return "example.com";
|
||||||
|
},
|
||||||
|
} as any,
|
||||||
|
});
|
||||||
|
const dnsProvider = {
|
||||||
|
usePunyCode() {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
async createRecord(recordReq: any) {
|
||||||
|
assert.equal(recordReq.domain, "sub.example.com");
|
||||||
|
assert.equal(recordReq.fullRecord, "certd-key.cname.sub.example.com");
|
||||||
|
assert.equal(recordReq.hostRecord, "certd-key.cname");
|
||||||
|
return { id: "record-id" };
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
await service.challengeCreateFn(
|
||||||
|
{
|
||||||
|
identifier: {
|
||||||
|
value: "www.example.com",
|
||||||
|
},
|
||||||
|
challenges: [
|
||||||
|
{
|
||||||
|
type: "dns-01",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
async () => "key-auth",
|
||||||
|
{
|
||||||
|
domainsVerifyPlan: {
|
||||||
|
"www.example.com": {
|
||||||
|
type: "cname",
|
||||||
|
domain: "www.example.com",
|
||||||
|
mainDomain: "example.com",
|
||||||
|
cnameVerifyPlan: {
|
||||||
|
domain: "cname.sub.example.com",
|
||||||
|
fullRecord: "certd-key.cname.sub.example.com",
|
||||||
|
dnsProvider,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepEqual(parseCalls, ["www.example.com", "certd-key.cname.sub.example.com"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -324,8 +324,8 @@ export class AcmeService {
|
|||||||
const cname: CnameVerifyPlan = domainVerifyPlan.cnameVerifyPlan;
|
const cname: CnameVerifyPlan = domainVerifyPlan.cnameVerifyPlan;
|
||||||
if (cname) {
|
if (cname) {
|
||||||
dnsProvider = cname.dnsProvider;
|
dnsProvider = cname.dnsProvider;
|
||||||
domain = await this.options.domainParser.parse(cname.domain);
|
|
||||||
fullRecord = cname.fullRecord;
|
fullRecord = cname.fullRecord;
|
||||||
|
domain = await this.options.domainParser.parse(fullRecord);
|
||||||
} else {
|
} else {
|
||||||
this.logger.error(`未找到域名${fullDomain}的CNAME校验计划,请修改证书申请配置`);
|
this.logger.error(`未找到域名${fullDomain}的CNAME校验计划,请修改证书申请配置`);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user