fix: zone resolution logic

This commit is contained in:
2026-05-21 14:46:37 +02:00
parent e7098015de
commit bdd851ffca
4 changed files with 70 additions and 25 deletions
+35 -14
View File
@@ -63,13 +63,14 @@ function isAcmeTagged(tags: Record<string, string> | undefined): boolean {
export class DnsChallengeManager {
private readonly client: DnsManagementClient;
private zoneMap: Map<string, string> | undefined; // zone name → resource group
constructor(credential: TokenCredential, private readonly config: Config) {
this.client = new DnsManagementClient(credential, config.subscriptionId);
}
async createTxtRecord(fqdn: string, value: string): Promise<void> {
const { resourceGroup, zone, name } = this.parseFqdn(fqdn);
const { resourceGroup, zone, name } = await this.resolveFqdn(fqdn);
await this.client.recordSets.createOrUpdate(resourceGroup, zone, name, 'TXT', {
ttl: this.config.dnsChallengeTtl,
txtRecords: [{ value: [value] }],
@@ -77,7 +78,7 @@ export class DnsChallengeManager {
}
async deleteTxtRecord(fqdn: string): Promise<void> {
const { resourceGroup, zone, name } = this.parseFqdn(fqdn);
const { resourceGroup, zone, name } = await this.resolveFqdn(fqdn);
try {
await this.client.recordSets.delete(resourceGroup, zone, name, 'TXT');
} catch {
@@ -85,20 +86,40 @@ export class DnsChallengeManager {
}
}
private parseFqdn(fqdn: string): { resourceGroup: string; zone: string; name: string } {
private async loadZoneMap(): Promise<Map<string, string>> {
if (this.zoneMap) return this.zoneMap;
this.zoneMap = new Map();
for (const rg of this.config.resourceGroups) {
for (const zone of this.config.dnsZones ?? []) {
if (fqdn.endsWith(`.${zone}`) || fqdn === zone) {
const name = fqdn === zone ? '@' : fqdn.slice(0, -(zone.length + 1));
return { resourceGroup: rg, zone, name };
}
for await (const zone of this.client.zones.listByResourceGroup(rg)) {
if (!zone.name) continue;
if (this.config.dnsZones && !this.config.dnsZones.includes(zone.name)) continue;
this.zoneMap.set(zone.name, rg);
}
}
// fallback: derive zone from fqdn by stripping first label
const parts = fqdn.split('.');
const zone = parts.slice(1).join('.');
const name = parts[0];
const rg = this.config.resourceGroups[0];
return { resourceGroup: rg, zone, name };
return this.zoneMap;
}
private async resolveFqdn(fqdn: string): Promise<{ resourceGroup: string; zone: string; name: string }> {
const zones = await this.loadZoneMap();
// longest-suffix match: find the most specific zone that is a suffix of fqdn
let bestZone = '';
for (const zoneName of zones.keys()) {
if (
(fqdn === zoneName || fqdn.endsWith(`.${zoneName}`)) &&
zoneName.length > bestZone.length
) {
bestZone = zoneName;
}
}
if (!bestZone) {
throw new Error(`No Azure DNS zone found for FQDN: ${fqdn}`);
}
const resourceGroup = zones.get(bestZone)!;
const name = fqdn === bestZone ? '@' : fqdn.slice(0, -(bestZone.length + 1));
return { resourceGroup, zone: bestZone, name };
}
}