From c677cd28f5d859d2b05334f100d35ab0c5b88b20 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Fri, 12 Dec 2025 12:20:42 -0800 Subject: [PATCH] Resole DNS --- .../pki-acme/pki-acme-challenge-service.ts | 30 +++++++++++-------- backend/src/lib/config/env.ts | 9 ++++++ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/backend/src/ee/services/pki-acme/pki-acme-challenge-service.ts b/backend/src/ee/services/pki-acme/pki-acme-challenge-service.ts index 4a112226a3..b24db33da1 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-challenge-service.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-challenge-service.ts @@ -1,3 +1,5 @@ +import { Resolver } from "node:dns/promises"; + import axios, { AxiosError } from "axios"; import { TPkiAcmeChallenges } from "@app/db/schemas/pki-acme-challenges"; @@ -107,22 +109,26 @@ export const pkiAcmeChallengeServiceFactory = ({ const expectedChallengeResponseBody = `${challenge.auth.token}.${thumbprint}`; if (challengeResponseBody.trimEnd() !== expectedChallengeResponseBody) { - throw new AcmeIncorrectResponseError({ message: "ACME challenge response is not correct" }); + throw new AcmeIncorrectResponseError({ message: "ACME HTTP-01 challenge response is not correct" }); } }; const validateDns01Challenge = async (challenge: ChallengeWithAuth): Promise => { - // TODO: Implement DNS-01 challenge validation - // DNS-01 challenge validation should: - // 1. Construct the TXT record name: _acme-challenge.{challenge.auth.identifierValue} - // 2. Query DNS for the TXT record - // 3. Verify the TXT record value matches: {challenge.auth.token}.{challenge.auth.account.publicKeyThumbprint} - // 4. Handle DNS propagation delays and retries - logger.info( - { challengeId: challenge.id, domain: challenge.auth.identifierValue }, - "DNS-01 challenge validation not yet implemented" - ); - throw new BadRequestError({ message: "DNS-01 challenge validation is not yet implemented" }); + const resolver = new Resolver(); + if (appCfg.ACME_DNS_RESOLVER_SERVERS.length > 0) { + resolver.setServers(appCfg.ACME_DNS_RESOLVER_SERVERS); + } + + const recordName = `_acme-challenge.${challenge.auth.identifierValue}`; + const records = await resolver.resolveTxt(recordName); + const recordValue = records.map((chunks) => chunks.join("")).join(""); + + const thumbprint = challenge.auth.account.publicKeyThumbprint; + const expectedChallengeResponseBody = `${challenge.auth.token}.${thumbprint}`; + + if (recordValue !== expectedChallengeResponseBody) { + throw new AcmeIncorrectResponseError({ message: "ACME DNS-01 challenge response is not correct" }); + } }; const handleChallengeValidationError = async ( diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index c47506514e..2fcee909ef 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -119,6 +119,15 @@ const envSchema = z }) .default("{}") ), + ACME_DNS_RESOLVER_SERVERS: zpStr( + z + .string() + .optional() + .transform((val) => { + if (!val) return []; + return val.split(","); + }) + ), DNS_MADE_EASY_SANDBOX_ENABLED: zodStrBool.default("false").optional(), // smtp options SMTP_HOST: zpStr(z.string().optional()),