fix: cnameProvider域名支持设置子域名托管

This commit is contained in:
xiaojunnuo
2026-05-10 22:41:07 +08:00
parent f93bc09438
commit 7266af1749
12 changed files with 143 additions and 7 deletions
@@ -0,0 +1 @@
ALTER TABLE cd_cname_provider ADD COLUMN subdomain varchar(100);
@@ -11,6 +11,8 @@ export class CnameProviderEntity {
userId: number;
@Column({ comment: '域名', length: 100 })
domain: string;
@Column({ comment: '子域名托管', length: 100, nullable: true })
subdomain: string;
@Column({ comment: 'DNS提供商类型', name: 'dns_provider_type', length: 20 })
dnsProviderType: string;
@Column({ comment: 'DNS授权Id', name: 'access_id' })
@@ -98,6 +98,18 @@ export class CnameProviderService extends BaseService<CnameProviderEntity> {
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[]> {
const list = await super.list(req);
const sysPrivateSettings = await this.settingsService.getSetting<SysPrivateSettings>(SysPrivateSettings);
@@ -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 { SubDomainService } from "../sub-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 {
userId: number;
projectId: number;
subDomainService: SubDomainService;
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.projectId = projectId;
this.subDomainService = subDomainService;
this.domainService = domainService;
this.cnameProviderService = cnameProviderService;
}
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) {
@@ -12,6 +12,7 @@ import { SubDomainService } from "../sub-domain-service.js";
import { CertInfoGetter } from "./cert-info-getter.js";
import { CertInfoService } from "../../../monitor/index.js";
import { ICertInfoGetter } from "@certd/plugin-lib";
import { CnameProviderService } from "../../../cname/service/cname-provider-service.js";
const serviceNames = [
'ocrService',
@@ -53,7 +54,8 @@ export class TaskServiceGetter implements IServiceGetter{
async getSubDomainsGetter(): Promise<SubDomainsGetter> {
const subDomainsService:SubDomainService = await this.appCtx.getAsync("subDomainService")
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> {
@@ -102,4 +104,3 @@ export type TaskServiceCreateReq = {
@@ -112,3 +112,65 @@ describe("AcmeService account config", () => {
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;
if (cname) {
dnsProvider = cname.dnsProvider;
domain = await this.options.domainParser.parse(cname.domain);
fullRecord = cname.fullRecord;
domain = await this.options.domainParser.parse(fullRecord);
} else {
this.logger.error(`未找到域名${fullDomain}的CNAME校验计划,请修改证书申请配置`);
}