拆分 DNS 权限验证与证书传播等待
This commit is contained in:
parent
d872d7eaaf
commit
f64f0d778f
@ -81,11 +81,7 @@ func NewWithConfigPath(ctx context.Context, cfg config.Config, configPath string
|
||||
_ = logger.Close()
|
||||
return nil, fmt.Errorf("初始化敏感信息存储失败:%w", err)
|
||||
}
|
||||
dnsTester := dnsprovider.NewTesterWithSettings(
|
||||
dnsprovider.NewLegoFactory(),
|
||||
time.Duration(cfg.ACME.PropagationTimeoutSecs)*time.Second,
|
||||
time.Duration(cfg.ACME.PropagationPollingIntervalSecs)*time.Second,
|
||||
)
|
||||
dnsTester := dnsprovider.NewTester(dnsprovider.NewLegoFactory())
|
||||
dnsVerification := setup.NewDNSVerificationManager(dnsTester, secretStore)
|
||||
repo := repository.NewSQLDomainRepository(db.DB)
|
||||
serverRepo := repository.NewSQLServerRepository(db.DB)
|
||||
|
||||
@ -106,7 +106,7 @@ func verifyExistingDNSCredentials(ctx context.Context, cfg config.Config, input
|
||||
if err != nil {
|
||||
return func() {}, fmt.Errorf("读取已有 DNS 凭据失败:%w", err)
|
||||
}
|
||||
verifyCtx, cancel := context.WithTimeout(ctx, dnsVerificationTimeout(cfg))
|
||||
verifyCtx, cancel := context.WithTimeout(ctx, dnsprovider.PermissionVerificationTimeout)
|
||||
defer cancel()
|
||||
if err := checker.VerifyDomain(verifyCtx, input.DNSProvider, target, credentials); err != nil {
|
||||
return func() {}, fmt.Errorf("已有 DNS 凭据在线权限校验失败:%w", err)
|
||||
|
||||
@ -476,26 +476,14 @@ func prepareDNSCredentials(ctx context.Context, cfg config.Config, input *setup.
|
||||
return func() {}, fmt.Errorf("初始化 DNS 凭据存储失败:%w", err)
|
||||
}
|
||||
manager := setup.NewDNSVerificationManager(
|
||||
dnsprovider.NewTesterWithSettings(
|
||||
dnsprovider.NewLegoFactory(),
|
||||
time.Duration(cfg.ACME.PropagationTimeoutSecs)*time.Second,
|
||||
time.Duration(cfg.ACME.PropagationPollingIntervalSecs)*time.Second,
|
||||
),
|
||||
dnsprovider.NewTester(dnsprovider.NewLegoFactory()),
|
||||
store,
|
||||
)
|
||||
return prepareDNSCredentialsWithManagerAndTimeout(ctx, input, output, manager, store, dnsVerificationTimeout(cfg))
|
||||
}
|
||||
|
||||
func dnsVerificationTimeout(cfg config.Config) time.Duration {
|
||||
timeout := time.Duration(cfg.ACME.PropagationTimeoutSecs)*time.Second + 30*time.Second
|
||||
if timeout <= 30*time.Second {
|
||||
return 45 * time.Second
|
||||
}
|
||||
return timeout
|
||||
return prepareDNSCredentialsWithManagerAndTimeout(ctx, input, output, manager, store, dnsprovider.PermissionVerificationTimeout)
|
||||
}
|
||||
|
||||
func prepareDNSCredentialsWithManager(ctx context.Context, input *setup.Input, output io.Writer, manager *setup.DNSVerificationManager, store *secretstore.Store) (func(), error) {
|
||||
return prepareDNSCredentialsWithManagerAndTimeout(ctx, input, output, manager, store, 45*time.Second)
|
||||
return prepareDNSCredentialsWithManagerAndTimeout(ctx, input, output, manager, store, dnsprovider.PermissionVerificationTimeout)
|
||||
}
|
||||
|
||||
func prepareDNSCredentialsWithManagerAndTimeout(ctx context.Context, input *setup.Input, output io.Writer, manager *setup.DNSVerificationManager, store *secretstore.Store, timeout time.Duration) (func(), error) {
|
||||
|
||||
@ -32,16 +32,22 @@ type Tester struct {
|
||||
propagationInterval time.Duration
|
||||
}
|
||||
|
||||
const dnsCleanupTimeout = 30 * time.Second
|
||||
const defaultDNSPropagationTimeout = 30 * time.Second
|
||||
const defaultDNSPropagationInterval = 2 * time.Second
|
||||
const (
|
||||
// PermissionVerificationTimeout 是 DNS API 权限验证的整体请求上限。
|
||||
// 它与 ACME DNS-01 的公网传播等待相互独立。
|
||||
PermissionVerificationTimeout = 60 * time.Second
|
||||
|
||||
dnsCleanupTimeout = 30 * time.Second
|
||||
defaultDNSPermissionTXTLookupTimeout = 30 * time.Second
|
||||
defaultDNSPermissionTXTLookupInterval = 2 * time.Second
|
||||
)
|
||||
|
||||
func NewTester(factory Factory) *Tester {
|
||||
return NewTesterWithResolverAndSettings(factory, legoTXTResolver{}, defaultDNSPropagationTimeout, defaultDNSPropagationInterval)
|
||||
return NewTesterWithResolverAndSettings(factory, legoTXTResolver{}, defaultDNSPermissionTXTLookupTimeout, defaultDNSPermissionTXTLookupInterval)
|
||||
}
|
||||
|
||||
func NewTesterWithResolver(factory Factory, resolver TXTResolver) *Tester {
|
||||
return NewTesterWithResolverAndSettings(factory, resolver, defaultDNSPropagationTimeout, defaultDNSPropagationInterval)
|
||||
return NewTesterWithResolverAndSettings(factory, resolver, defaultDNSPermissionTXTLookupTimeout, defaultDNSPermissionTXTLookupInterval)
|
||||
}
|
||||
|
||||
func NewTesterWithSettings(factory Factory, timeout, interval time.Duration) *Tester {
|
||||
@ -50,10 +56,10 @@ func NewTesterWithSettings(factory Factory, timeout, interval time.Duration) *Te
|
||||
|
||||
func NewTesterWithResolverAndSettings(factory Factory, resolver TXTResolver, timeout, interval time.Duration) *Tester {
|
||||
if timeout <= 0 {
|
||||
timeout = defaultDNSPropagationTimeout
|
||||
timeout = defaultDNSPermissionTXTLookupTimeout
|
||||
}
|
||||
if interval <= 0 || interval > timeout {
|
||||
interval = defaultDNSPropagationInterval
|
||||
interval = defaultDNSPermissionTXTLookupInterval
|
||||
if interval > timeout {
|
||||
interval = timeout
|
||||
}
|
||||
@ -192,10 +198,10 @@ func waitForTXT(ctx context.Context, resolver TXTResolver, fqdn, expected string
|
||||
return errors.New("TXT 查询服务未初始化")
|
||||
}
|
||||
if timeout <= 0 {
|
||||
timeout = defaultDNSPropagationTimeout
|
||||
timeout = defaultDNSPermissionTXTLookupTimeout
|
||||
}
|
||||
if interval <= 0 || interval > timeout {
|
||||
interval = defaultDNSPropagationInterval
|
||||
interval = defaultDNSPermissionTXTLookupInterval
|
||||
if interval > timeout {
|
||||
interval = timeout
|
||||
}
|
||||
|
||||
@ -80,7 +80,7 @@ func TestAdminDNSTestRejectsUnavailableService(t *testing.T) {
|
||||
if response.Code != http.StatusServiceUnavailable {
|
||||
t.Fatalf("DNS 服务未初始化时应返回 503,实际为 %d:%s", response.Code, response.Body.String())
|
||||
}
|
||||
if !strings.Contains(response.Body.String(), `"verification_timeout_secs":930`) {
|
||||
if !strings.Contains(response.Body.String(), `"verification_timeout_secs":60`) {
|
||||
t.Fatalf("DNS 服务未初始化响应应包含当前最大等待时间:%s", response.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ func (s *Server) longOperation(timeout time.Duration) gin.HandlerFunc {
|
||||
}
|
||||
|
||||
func (s *Server) dnsOperation(c *gin.Context) {
|
||||
s.longOperation(s.dnsVerificationTimeout())(c)
|
||||
s.longOperation(dnsAPIPermissionVerificationTimeout())(c)
|
||||
}
|
||||
|
||||
func (s *Server) requireSameOrigin(c *gin.Context) {
|
||||
|
||||
@ -157,7 +157,7 @@ func (s *Server) testAdminDNS(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (s *Server) testDNS(c *gin.Context) {
|
||||
verificationTimeoutSecs := int(s.dnsVerificationTimeout().Seconds())
|
||||
verificationTimeoutSecs := int(dnsAPIPermissionVerificationTimeout().Seconds())
|
||||
if s.setupDNS == nil {
|
||||
s.writeDNSTestError(c, http.StatusServiceUnavailable, "DNS 在线验证服务尚未初始化", nil, verificationTimeoutSecs)
|
||||
return
|
||||
@ -167,7 +167,7 @@ func (s *Server) testDNS(c *gin.Context) {
|
||||
s.writeDNSTestError(c, http.StatusBadRequest, "DNS 测试请求参数无效", err, verificationTimeoutSecs)
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), s.dnsVerificationTimeout())
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), dnsAPIPermissionVerificationTimeout())
|
||||
defer cancel()
|
||||
result, err := s.setupDNS.Verify(ctx, setup.DNSVerificationInput{
|
||||
ProviderID: request.ProviderID, TestDomain: request.TestDomain, Credentials: request.Credentials,
|
||||
@ -216,20 +216,10 @@ func (s *Server) writeDNSTestError(c *gin.Context, status int, message string, e
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) dnsVerificationTimeout() time.Duration {
|
||||
s.configMu.RLock()
|
||||
cfg := s.config
|
||||
s.configMu.RUnlock()
|
||||
timeout := time.Duration(cfg.ACME.PropagationTimeoutSecs)*time.Second + dnsCleanupRequestBuffer
|
||||
if timeout <= dnsCleanupRequestBuffer {
|
||||
return defaultDNSRequestTimeout
|
||||
}
|
||||
return timeout
|
||||
func dnsAPIPermissionVerificationTimeout() time.Duration {
|
||||
return dnsprovider.PermissionVerificationTimeout
|
||||
}
|
||||
|
||||
const defaultDNSRequestTimeout = 45 * time.Second
|
||||
const dnsCleanupRequestBuffer = 30 * time.Second
|
||||
|
||||
func (s *Server) applySetup(c *gin.Context) {
|
||||
if !s.setupMu.TryLock() {
|
||||
s.writeError(c, http.StatusConflict, "初始化正在执行,请等待当前操作完成", nil)
|
||||
|
||||
@ -239,16 +239,16 @@ func TestSetupDNSProviderCatalogAndOnlineTest(t *testing.T) {
|
||||
!strings.Contains(testResponse.Body.String(), "清理记录") {
|
||||
t.Fatalf("DNS 在线测试响应应包含阶段报告:%s", testResponse.Body.String())
|
||||
}
|
||||
if !strings.Contains(testResponse.Body.String(), `"verification_timeout_secs":930`) {
|
||||
if !strings.Contains(testResponse.Body.String(), `"verification_timeout_secs":60`) {
|
||||
t.Fatalf("DNS 在线测试响应应包含当前最大等待时间:%s", testResponse.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetupDNSTestUsesConfiguredRequestTimeout(t *testing.T) {
|
||||
func TestSetupDNSTestUsesShortPermissionVerificationTimeout(t *testing.T) {
|
||||
baseDir := t.TempDir()
|
||||
cfg := config.Default(baseDir)
|
||||
cfg.ACME.PropagationTimeoutSecs = 1
|
||||
cfg.ACME.PropagationPollingIntervalSecs = 1
|
||||
cfg.ACME.PropagationTimeoutSecs = 86400
|
||||
cfg.ACME.PropagationPollingIntervalSecs = 60
|
||||
verifier := &fakeSetupDNSVerifier{verifyErr: errors.New("测试 DNS 验证失败")}
|
||||
server := New(Dependencies{Config: cfg, Repository: &stubDomainRepository{}, SetupDNS: verifier})
|
||||
|
||||
@ -265,15 +265,15 @@ func TestSetupDNSTestUsesConfiguredRequestTimeout(t *testing.T) {
|
||||
if response.Code != http.StatusUnprocessableEntity {
|
||||
t.Fatalf("模拟 DNS 验证失败应返回 422,实际为 %d:%s", response.Code, response.Body.String())
|
||||
}
|
||||
if !strings.Contains(response.Body.String(), `"verification_timeout_secs":31`) {
|
||||
if !strings.Contains(response.Body.String(), `"verification_timeout_secs":60`) {
|
||||
t.Fatalf("DNS 验证失败响应应包含当前最大等待时间:%s", response.Body.String())
|
||||
}
|
||||
if !verifier.verifyHasDeadline {
|
||||
t.Fatal("DNS 在线测试应收到带截止时间的上下文")
|
||||
}
|
||||
remaining := verifier.verifyDeadline.Sub(started)
|
||||
if remaining < 30*time.Second || remaining > 32*time.Second {
|
||||
t.Fatalf("DNS 在线测试请求超时未按配置计算:剩余时间=%s", remaining)
|
||||
if remaining < 59*time.Second || remaining > 61*time.Second {
|
||||
t.Fatalf("DNS 在线测试请求超时不应继承 ACME 传播配置:剩余时间=%s", remaining)
|
||||
}
|
||||
}
|
||||
func TestSetupApplyPersistsConfigurationWithoutPlainPassword(t *testing.T) {
|
||||
@ -527,7 +527,7 @@ func TestSetupDNSTestRejectsRemoteAndOversizedRequests(t *testing.T) {
|
||||
if oversizedResponse.Code != http.StatusBadRequest {
|
||||
t.Fatalf("超大 DNS 测试请求应被拒绝:%d %s", oversizedResponse.Code, oversizedResponse.Body.String())
|
||||
}
|
||||
if !strings.Contains(oversizedResponse.Body.String(), `"verification_timeout_secs":930`) {
|
||||
if !strings.Contains(oversizedResponse.Body.String(), `"verification_timeout_secs":60`) {
|
||||
t.Fatalf("DNS 参数错误响应应包含当前最大等待时间:%s", oversizedResponse.Body.String())
|
||||
}
|
||||
}
|
||||
@ -547,7 +547,7 @@ func TestSetupDNSTestReturnsNotImplementedForUnavailableProvider(t *testing.T) {
|
||||
if strings.Contains(response.Body.String(), "access_key_secret") || strings.Contains(response.Body.String(), "secret") {
|
||||
t.Fatal("未开放 Provider 响应不应包含提交的凭据")
|
||||
}
|
||||
if !strings.Contains(response.Body.String(), `"verification_timeout_secs":930`) {
|
||||
if !strings.Contains(response.Body.String(), `"verification_timeout_secs":60`) {
|
||||
t.Fatalf("未开放 Provider 响应应包含当前最大等待时间:%s", response.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
||||
|
||||
const defaultDNSVerificationTimeoutSeconds = 45;
|
||||
const dnsVerificationCleanupBufferSeconds = 30;
|
||||
const defaultDNSVerificationTimeoutSeconds = 60;
|
||||
|
||||
export function useDashboard() {
|
||||
const scertLogo = "/Scert.png";
|
||||
@ -218,14 +217,6 @@ export function useDashboard() {
|
||||
new Date(dns.expires_at).getTime() > Date.now();
|
||||
});
|
||||
|
||||
function resolveDNSVerificationTimeoutSeconds(value) {
|
||||
const propagationTimeout = Number(value);
|
||||
if (!Number.isFinite(propagationTimeout) || propagationTimeout <= 0) {
|
||||
return defaultDNSVerificationTimeoutSeconds;
|
||||
}
|
||||
return Math.floor(propagationTimeout) + dnsVerificationCleanupBufferSeconds;
|
||||
}
|
||||
|
||||
function updateDNSVerificationTimeoutSeconds(value) {
|
||||
const timeout = Number(value);
|
||||
if (Number.isFinite(timeout) && timeout > 0) {
|
||||
@ -244,9 +235,7 @@ export function useDashboard() {
|
||||
|
||||
function startDNSVerificationTimer(requestVersion) {
|
||||
stopDNSVerificationTimer();
|
||||
dnsVerificationTimeoutSeconds.value = resolveDNSVerificationTimeoutSeconds(
|
||||
settingsForm.value.acme.propagation_timeout_secs,
|
||||
);
|
||||
dnsVerificationTimeoutSeconds.value = defaultDNSVerificationTimeoutSeconds;
|
||||
const startedAt = Date.now();
|
||||
const updateElapsedSeconds = () => {
|
||||
if (requestVersion !== dnsRequestVersion) return;
|
||||
|
||||
@ -103,7 +103,7 @@ test("管理设置 DNS 验证显示等待进度,并在结束后停止计时",
|
||||
|
||||
assert.equal(state.dnsTesting.value, true);
|
||||
assert.equal(state.dnsVerificationElapsedSeconds.value, 0);
|
||||
assert.equal(state.dnsVerificationTimeoutSeconds.value, 930);
|
||||
assert.equal(state.dnsVerificationTimeoutSeconds.value, 60);
|
||||
advanceTime(3200);
|
||||
runIntervals();
|
||||
assert.equal(state.dnsVerificationElapsedSeconds.value, 3);
|
||||
@ -113,12 +113,12 @@ test("管理设置 DNS 验证显示等待进度,并在结束后停止计时",
|
||||
credential_ref: "secret://test/only",
|
||||
verification_token: "test-token",
|
||||
expires_at: new Date(Date.now() + 60000).toISOString(),
|
||||
verification_timeout_secs: 31,
|
||||
verification_timeout_secs: 60,
|
||||
}));
|
||||
await task;
|
||||
assert.equal(state.dnsTesting.value, false);
|
||||
assert.equal(state.dnsVerificationElapsedSeconds.value, 0);
|
||||
assert.equal(state.dnsVerificationTimeoutSeconds.value, 31);
|
||||
assert.equal(state.dnsVerificationTimeoutSeconds.value, 60);
|
||||
advanceTime(1000);
|
||||
runIntervals();
|
||||
assert.equal(state.dnsVerificationElapsedSeconds.value, 0);
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
|
||||
|
||||
const defaultDNSVerificationTimeoutSeconds = 45;
|
||||
const dnsVerificationCleanupBufferSeconds = 30;
|
||||
const defaultDNSVerificationTimeoutSeconds = 60;
|
||||
|
||||
const commonEmailDomains = [
|
||||
{ label: "常用邮箱", value: "" },
|
||||
@ -155,14 +154,6 @@ export function useSetupWizard(props, emit) {
|
||||
dnsTestError.value = "";
|
||||
}
|
||||
|
||||
function resolveDNSVerificationTimeoutSeconds(value) {
|
||||
const propagationTimeout = Number(value);
|
||||
if (!Number.isFinite(propagationTimeout) || propagationTimeout <= 0) {
|
||||
return defaultDNSVerificationTimeoutSeconds;
|
||||
}
|
||||
return Math.floor(propagationTimeout) + dnsVerificationCleanupBufferSeconds;
|
||||
}
|
||||
|
||||
function updateDNSVerificationTimeoutSeconds(value) {
|
||||
const timeout = Number(value);
|
||||
if (Number.isFinite(timeout) && timeout > 0) {
|
||||
@ -181,9 +172,7 @@ export function useSetupWizard(props, emit) {
|
||||
|
||||
function startDNSVerificationTimer(requestVersion) {
|
||||
stopDNSVerificationTimer();
|
||||
dnsVerificationTimeoutSeconds.value = resolveDNSVerificationTimeoutSeconds(
|
||||
form.value.propagation_timeout_secs,
|
||||
);
|
||||
dnsVerificationTimeoutSeconds.value = defaultDNSVerificationTimeoutSeconds;
|
||||
const startedAt = Date.now();
|
||||
const updateElapsedSeconds = () => {
|
||||
if (requestVersion !== dnsRequestVersion) return;
|
||||
|
||||
@ -34,7 +34,7 @@ test("初始化 DNS 验证显示等待进度,并采用服务端返回的最长
|
||||
|
||||
assert.equal(state.dnsTesting.value, true);
|
||||
assert.equal(state.dnsVerificationElapsedSeconds.value, 0);
|
||||
assert.equal(state.dnsVerificationTimeoutSeconds.value, 930);
|
||||
assert.equal(state.dnsVerificationTimeoutSeconds.value, 60);
|
||||
|
||||
advanceTime(2500);
|
||||
runIntervals();
|
||||
@ -45,12 +45,12 @@ test("初始化 DNS 验证显示等待进度,并采用服务端返回的最长
|
||||
credential_ref: "secret://test/only",
|
||||
verification_token: "test-token",
|
||||
expires_at: new Date(Date.now() + 60000).toISOString(),
|
||||
verification_timeout_secs: 31,
|
||||
verification_timeout_secs: 60,
|
||||
}));
|
||||
await task;
|
||||
assert.equal(state.dnsTesting.value, false);
|
||||
assert.equal(state.dnsVerificationElapsedSeconds.value, 0);
|
||||
assert.equal(state.dnsVerificationTimeoutSeconds.value, 31);
|
||||
assert.equal(state.dnsVerificationTimeoutSeconds.value, 60);
|
||||
|
||||
advanceTime(1000);
|
||||
runIntervals();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user