perf: 增加权威NS检查开关,某些用户服务器禁止向黑名单NS服务器发请求

This commit is contained in:
xiaojunnuo
2026-04-27 00:16:14 +08:00
parent eab66e2d19
commit 1aa50cf53a
13 changed files with 242 additions and 180 deletions
+1 -1
View File
@@ -494,7 +494,7 @@ class AcmeClient {
throw new Error('Unable to verify ACME challenge, URL not found'); throw new Error('Unable to verify ACME challenge, URL not found');
} }
const {challenges} = createChallengeFn({logger:this.logger}); const {challenges} = createChallengeFn({logger:this.logger,walkFromAuthoritative: this.opts.walkFromAuthoritative});
const verify = challenges const verify = challenges
if (typeof verify[challenge.type] === 'undefined') { if (typeof verify[challenge.type] === 'undefined') {
+3 -1
View File
@@ -252,7 +252,7 @@ async function resolveDomainBySoaRecord(recordName, logger = log) {
async function getAuthoritativeDnsResolver(recordName, logger = log) { async function getAuthoritativeDnsResolver(recordName, logger = log) {
logger(`获取域名${recordName}的权威NS服务器: `); logger(`获取域名${recordName}的权威NS服务器: `);
const resolver = new dns.Resolver({ timeout: 10000,maxTimeout: 60000 }); const resolver = new dns.Resolver({timeout: 2000,tries: 2});
try { try {
/* Resolve root domain by SOA */ /* Resolve root domain by SOA */
@@ -352,3 +352,5 @@ export {
resolveDomainBySoaRecord resolveDomainBySoaRecord
}; };
+15 -2
View File
@@ -12,6 +12,10 @@ import {utils} from '@certd/basic'
const dns = dnsSdk.promises const dns = dnsSdk.promises
let walkFromAuthoritative = true
export function setWalkFromAuthoritative(value = true) {
walkFromAuthoritative = value
}
export function createChallengeFn(opts = {}) { export function createChallengeFn(opts = {}) {
const logger = opts?.logger || { info: defaultLog, error: defaultLog, warn: defaultLog, debug: defaultLog } const logger = opts?.logger || { info: defaultLog, error: defaultLog, warn: defaultLog, debug: defaultLog }
@@ -88,9 +92,10 @@ async function walkDnsChallengeRecord(recordName, resolver = dns,deep = 0) {
let records = []; let records = [];
const isAuthoritative = resolver === dns
/* Resolve TXT records */ /* Resolve TXT records */
try { try {
log(`检查域名 ${recordName} 的TXT记录`); log(`检查域名 ${recordName} 的TXT记录(from ${isAuthoritative ? '本地DNS' : '权威DNS服务器'})`);
const txtRecords = await resolver.resolveTxt(recordName); const txtRecords = await resolver.resolveTxt(recordName);
if (txtRecords && txtRecords.length) { if (txtRecords && txtRecords.length) {
log(`找到 ${txtRecords.length} 条 TXT记录( ${recordName}`); log(`找到 ${txtRecords.length} 条 TXT记录( ${recordName}`);
@@ -144,6 +149,7 @@ async function walkDnsChallengeRecord(recordName, resolver = dns,deep = 0) {
log(`本地获取TXT解析记录失败:${e.message}`) log(`本地获取TXT解析记录失败:${e.message}`)
} }
if (walkFromAuthoritative !==false) {
try { try {
/* Authoritative DNS resolver */ /* Authoritative DNS resolver */
log(`从域名权威服务器获取TXT解析记录`); log(`从域名权威服务器获取TXT解析记录`);
@@ -157,6 +163,8 @@ async function walkDnsChallengeRecord(recordName, resolver = dns,deep = 0) {
} catch (e) { } catch (e) {
log(`权威服务器获取TXT解析记录失败:${e.message}`) log(`权威服务器获取TXT解析记录失败:${e.message}`)
} }
}
if (txtRecords.length === 0) { if (txtRecords.length === 0) {
throw new Error(`没有找到TXT解析记录(${recordName}`); throw new Error(`没有找到TXT解析记录(${recordName}`);
@@ -179,7 +187,7 @@ async function walkDnsChallengeRecord(recordName, resolver = dns,deep = 0) {
async function verifyDnsChallenge(authz, challenge, keyAuthorization, prefix = '_acme-challenge.') { async function verifyDnsChallenge(authz, challenge, keyAuthorization, prefix = '_acme-challenge.') {
const recordName = `${prefix}${authz.identifier.value}`; const recordName = `${prefix}${authz.identifier.value}`;
log(`本地校验TXT记录): ${recordName}`); log(`本地校验TXT记录): ${recordName}`);
let recordValues = await walkTxtRecord(recordName); let recordValues = await walkTxtRecord(recordName, 0, walkFromAuthoritative);
//去重 //去重
recordValues = [...new Set(recordValues)]; recordValues = [...new Set(recordValues)];
log(`DNS查询成功, 找到 ${recordValues.length} 条TXT记录:${recordValues}`); log(`DNS查询成功, 找到 ${recordValues.length} 条TXT记录:${recordValues}`);
@@ -226,6 +234,11 @@ async function verifyTlsAlpnChallenge(authz, challenge, keyAuthorization) {
'tls-alpn-01': verifyTlsAlpnChallenge, 'tls-alpn-01': verifyTlsAlpnChallenge,
}, },
walkTxtRecord, walkTxtRecord,
walkDnsChallengeRecord,
} }
} }
// createChallengeFn({logger:{info:console.log}}).walkDnsChallengeRecord("handsfree.work")
+2
View File
@@ -220,3 +220,5 @@ export function getAuthoritativeDnsResolver(record:string): Promise<any>;
export const CancelError: typeof CancelError; export const CancelError: typeof CancelError;
export function resolveDomainBySoaRecord(domain: string): Promise<string>; export function resolveDomainBySoaRecord(domain: string): Promise<string>;
export function setWalkFromAuthoritative(value = true): void;
@@ -92,6 +92,9 @@ export class SysPrivateSettings extends BaseSettings {
environmentVars?: string = ''; environmentVars?: string = '';
acmeWalkFromAuthoritative?: boolean = true;
sms?: { sms?: {
type?: string; type?: string;
config?: any; config?: any;
@@ -4,7 +4,7 @@ import { Repository } from 'typeorm';
import { SysSettingsEntity } from '../entity/sys-settings.js'; import { SysSettingsEntity } from '../entity/sys-settings.js';
import { BaseSettings, SysInstallInfo, SysPrivateSettings, SysPublicSettings, SysSecret, SysSecretBackup } from './models.js'; import { BaseSettings, SysInstallInfo, SysPrivateSettings, SysPublicSettings, SysSecret, SysSecretBackup } from './models.js';
import { getAllSslProviderDomains, setSslProviderReverseProxies } from '@certd/acme-client'; import { getAllSslProviderDomains, setSslProviderReverseProxies, setWalkFromAuthoritative } from '@certd/acme-client';
import { cache, logger, mergeUtils, setGlobalProxy } from '@certd/basic'; import { cache, logger, mergeUtils, setGlobalProxy } from '@certd/basic';
import { isPlus } from '@certd/plus-core'; import { isPlus } from '@certd/plus-core';
import * as dns from 'node:dns'; import * as dns from 'node:dns';
@@ -180,6 +180,9 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
//加载环境变量 //加载环境变量
this.setEnvironmentVars(privateSetting.environmentVars); this.setEnvironmentVars(privateSetting.environmentVars);
setWalkFromAuthoritative(privateSetting.acmeWalkFromAuthoritative);
} }
setEnvironmentVars(vars: string) { setEnvironmentVars(vars: string) {
@@ -749,6 +749,18 @@ export default {
pipelineValidTimeEnabledHelper: "Whether to enable the valid time of the pipeline", pipelineValidTimeEnabledHelper: "Whether to enable the valid time of the pipeline",
certDomainAddToMonitorEnabled: "Add Domain to Certificate Monitor", certDomainAddToMonitorEnabled: "Add Domain to Certificate Monitor",
certDomainAddToMonitorEnabledHelper: "Whether to add the domain to the certificate monitor", certDomainAddToMonitorEnabledHelper: "Whether to add the domain to the certificate monitor",
defaultCertRenewDays: "Default Certificate Renew Days",
defaultCertRenewDaysHelper: "Default certificate renewal days, helpful for table list progress bar display",
defaultCertRenewDaysRecommend: "Recommend 15",
pipelineMaxRunningCount: "Max Running Count",
pipelineMaxRunningCountHelper: "Max running count of the pipeline",
pipelineMaxRunningCountRecommend: "Recommend 5-15, default 10",
acmeWalkFromAuthoritative: "Check TXT Record from Authoritative NS",
acmeWalkFromAuthoritativeHelper: "Apply certificate when whether to check the TXT record from authoritative NS server first",
fixedCertExpireDays: "Fixed Cert Expire Days", fixedCertExpireDays: "Fixed Cert Expire Days",
fixedCertExpireDaysHelper: "Fixed cert expiration days, helpful for table list progress bar display", fixedCertExpireDaysHelper: "Fixed cert expiration days, helpful for table list progress bar display",
fixedCertExpireDaysRecommend: "Recommend 90", fixedCertExpireDaysRecommend: "Recommend 90",
@@ -760,6 +760,8 @@ export default {
pipelineMaxRunningCount: "同时最大运行流水线数量", pipelineMaxRunningCount: "同时最大运行流水线数量",
pipelineMaxRunningCountHelper: "同一个用户同时运行的最大流水线数量,避免同时触发太多导致ACME账户被限制", pipelineMaxRunningCountHelper: "同一个用户同时运行的最大流水线数量,避免同时触发太多导致ACME账户被限制",
pipelineMaxRunningCountRecommend: "推荐5-15,默认10", pipelineMaxRunningCountRecommend: "推荐5-15,默认10",
acmeWalkFromAuthoritative: "从权威NS检查TXT记录",
acmeWalkFromAuthoritativeHelper: "申请证书时,是否从权威NS服务器检查TXT记录,如果影响申请证书,可以关闭",
fixedCertExpireDays: "固定证书有效期天数", fixedCertExpireDays: "固定证书有效期天数",
fixedCertExpireDaysHelper: "固定证书有效期天数,有助于列表进度条整齐显示", fixedCertExpireDaysHelper: "固定证书有效期天数,有助于列表进度条整齐显示",
@@ -807,6 +809,7 @@ export default {
environmentVars: "环境变量", environmentVars: "环境变量",
environmentVarsHelper: "配置运行时环境变量,每行一个,格式:KEY=VALUE", environmentVarsHelper: "配置运行时环境变量,每行一个,格式:KEY=VALUE",
bindUrl: "绑定URL", bindUrl: "绑定URL",
bindUrlHelper: "绑定URL,在各类通知中显示你的站点URL",
}, },
}, },
modal: { modal: {
@@ -109,6 +109,7 @@ export type SysPrivateSetting = {
type?: string; type?: string;
config?: any; config?: any;
}; };
acmeWalkFromAuthoritative?: boolean;
//http请求超时时间 //http请求超时时间
httpRequestTimeout?: number; httpRequestTimeout?: number;
@@ -303,8 +303,18 @@ export const useSettingStore = defineStore({
} }
}; };
const { closable = false } = opts; const { closable = false } = opts;
let title = "URL地址未绑定,是否绑定此地址?";
let okButtonText = "不,回到原来的地址";
let okButtonDanger = false;
let forceBack = true;
if (closable) {
title = "绑定URL";
okButtonText = "确定";
okButtonDanger = false;
forceBack = false;
}
const modalRef: any = Modal.warning({ const modalRef: any = Modal.warning({
title: "URL地址未绑定,是否绑定此地址?", title: title,
width: 500, width: 500,
keyboard: false, keyboard: false,
closable, closable,
@@ -320,6 +330,7 @@ export const useSettingStore = defineStore({
1 1
</a-button> </a-button>
</div> </div>
<div class="helper">1URL显示</div>
<div class="flex items-center justify-between mt-3"> <div class="flex items-center justify-between mt-3">
<span> <span>
2 2
@@ -334,12 +345,14 @@ export const useSettingStore = defineStore({
}, },
onOk: async () => { onOk: async () => {
// await this.doBindUrl(); // await this.doBindUrl();
if (forceBack) {
window.location.href = bindUrl; window.location.href = bindUrl;
}
}, },
okButtonProps: { okButtonProps: {
danger: true, danger: okButtonDanger,
}, },
okText: "不,回到原来的地址", okText: okButtonText,
// cancelText: "不,回到原来的地址", // cancelText: "不,回到原来的地址",
// onOk: () => { // onOk: () => {
// window.location.href = bindUrl; // window.location.href = bindUrl;
@@ -27,6 +27,7 @@
<a-form-item :label="t('certd.sys.setting.bindUrl')"> <a-form-item :label="t('certd.sys.setting.bindUrl')">
<a-button class="ml-2" type="primary" @click="settingsStore.openBindUrlModal({ closable: true })">{{ t("certd.sys.setting.bindUrl") }}</a-button> <a-button class="ml-2" type="primary" @click="settingsStore.openBindUrlModal({ closable: true })">{{ t("certd.sys.setting.bindUrl") }}</a-button>
<div class="helper" v-html="t('certd.sys.setting.bindUrlHelper')"></div>
</a-form-item> </a-form-item>
<a-form-item label=" " :colon="false" :wrapper-col="{ span: 8 }"> <a-form-item label=" " :colon="false" :wrapper-col="{ span: 8 }">
@@ -53,6 +53,13 @@
<div class="helper">{{ t("certd.sys.setting.pipelineMaxRunningCountHelper") }}</div> <div class="helper">{{ t("certd.sys.setting.pipelineMaxRunningCountHelper") }}</div>
</a-form-item> </a-form-item>
<a-form-item :label="t('certd.sys.setting.acmeWalkFromAuthoritative')" :name="['private', 'acmeWalkFromAuthoritative']">
<div class="flex items-center">
<a-switch v-model:checked="formState.private.acmeWalkFromAuthoritative" />
</div>
<div class="helper">{{ t("certd.sys.setting.acmeWalkFromAuthoritativeHelper") }}</div>
</a-form-item>
<a-form-item label=" " :colon="false" :wrapper-col="{ span: 8 }"> <a-form-item label=" " :colon="false" :wrapper-col="{ span: 8 }">
<a-button :loading="saveLoading" type="primary" html-type="submit">{{ t("certd.saveButton") }}</a-button> <a-button :loading="saveLoading" type="primary" html-type="submit">{{ t("certd.saveButton") }}</a-button>
</a-form-item> </a-form-item>
@@ -76,7 +83,9 @@ defineOptions({
const formState = reactive<Partial<SysSettings>>({ const formState = reactive<Partial<SysSettings>>({
public: {}, public: {},
private: {}, private: {
acmeWalkFromAuthoritative: true,
},
}); });
async function loadSysSettings() { async function loadSysSettings() {