补充DNS过期凭据清理重试
修复DNS验证会话过期后临时凭据清理失败的可恢复性,保留原会话以便重试且不恢复过期授权,补充回归测试并更新四份整改清单。
This commit is contained in:
parent
4ec81eecab
commit
f9dcb37763
@ -79,6 +79,11 @@ type dnsVerificationSession struct {
|
||||
ExpiresAt time.Time
|
||||
}
|
||||
|
||||
type expiredDNSVerificationSession struct {
|
||||
Token string
|
||||
Session dnsVerificationSession
|
||||
}
|
||||
|
||||
// DNSVerificationSession 仅供服务端完整初始化事务读取验证结果。
|
||||
// 所有字段均禁止 JSON 序列化,避免凭据引用被误写入 HTTP 响应。
|
||||
type DNSVerificationSession struct {
|
||||
@ -89,20 +94,21 @@ type DNSVerificationSession struct {
|
||||
}
|
||||
|
||||
type DNSVerificationManager struct {
|
||||
mu sync.Mutex
|
||||
tester DNSCredentialTester
|
||||
store SecretWriter
|
||||
deleter SecretDeleter
|
||||
now func() time.Time
|
||||
sessions map[string]dnsVerificationSession
|
||||
inFlight int
|
||||
mu sync.Mutex
|
||||
tester DNSCredentialTester
|
||||
store SecretWriter
|
||||
deleter SecretDeleter
|
||||
now func() time.Time
|
||||
sessions map[string]dnsVerificationSession
|
||||
expiredSession map[string]expiredDNSVerificationSession
|
||||
inFlight int
|
||||
}
|
||||
|
||||
func NewDNSVerificationManager(tester DNSCredentialTester, store SecretWriter) *DNSVerificationManager {
|
||||
deleter, _ := store.(SecretDeleter)
|
||||
return &DNSVerificationManager{
|
||||
tester: tester, store: store, deleter: deleter, now: time.Now,
|
||||
sessions: make(map[string]dnsVerificationSession),
|
||||
sessions: make(map[string]dnsVerificationSession), expiredSession: make(map[string]expiredDNSVerificationSession),
|
||||
}
|
||||
}
|
||||
|
||||
@ -396,7 +402,10 @@ func (m *DNSVerificationManager) clearExpiredExceptLocked(now time.Time, exclude
|
||||
}
|
||||
if !session.ExpiresAt.After(now) {
|
||||
delete(m.sessions, token)
|
||||
references = append(references, session.CredentialRef)
|
||||
if reference := strings.TrimSpace(session.CredentialRef); reference != "" {
|
||||
m.expiredSession[reference] = expiredDNSVerificationSession{Token: token, Session: session}
|
||||
references = append(references, reference)
|
||||
}
|
||||
}
|
||||
}
|
||||
return references
|
||||
@ -416,11 +425,39 @@ func (m *DNSVerificationManager) restoreSession(token string, session dnsVerific
|
||||
func (m *DNSVerificationManager) deleteCredentials(references []string) {
|
||||
for _, reference := range references {
|
||||
if err := m.deleteCredential(reference); err != nil {
|
||||
m.restoreExpiredSession(reference)
|
||||
m.logCleanupFailure()
|
||||
continue
|
||||
}
|
||||
m.forgetExpiredSession(reference)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *DNSVerificationManager) restoreExpiredSession(reference string) {
|
||||
if m == nil || strings.TrimSpace(reference) == "" {
|
||||
return
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
expired, ok := m.expiredSession[reference]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if _, exists := m.sessions[expired.Token]; !exists {
|
||||
m.sessions[expired.Token] = expired.Session
|
||||
}
|
||||
delete(m.expiredSession, reference)
|
||||
}
|
||||
|
||||
func (m *DNSVerificationManager) forgetExpiredSession(reference string) {
|
||||
if m == nil || strings.TrimSpace(reference) == "" {
|
||||
return
|
||||
}
|
||||
m.mu.Lock()
|
||||
delete(m.expiredSession, reference)
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
func (m *DNSVerificationManager) logCleanupFailure() {
|
||||
log.Print("DNS 临时凭据回收失败")
|
||||
}
|
||||
|
||||
@ -546,3 +546,33 @@ func TestDNSVerificationResolveIsServerOnlyAndDoesNotConsumeToken(t *testing.T)
|
||||
t.Fatal("已消费的 DNS 验证令牌仍可被解析")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDNSVerificationManagerRetriesExpiredCredentialCleanup(t *testing.T) {
|
||||
store := &retryableSecretWriter{failDelete: true}
|
||||
manager := NewDNSVerificationManager(fakeDNSTester{}, store)
|
||||
now := time.Date(2026, 9, 30, 12, 0, 0, 0, time.UTC)
|
||||
manager.now = func() time.Time { return now }
|
||||
result, err := manager.Verify(t.Context(), DNSVerificationInput{
|
||||
ProviderID: "cloudflare", TestDomain: "example.com",
|
||||
Credentials: map[string]string{"api_token": "secret"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
now = now.Add(dnsVerificationLifetime + time.Second)
|
||||
if err := manager.Check(result.VerificationToken, "cloudflare", result.CredentialRef, "example.com"); err == nil {
|
||||
t.Fatal("expired DNS verification token should be rejected")
|
||||
}
|
||||
if len(manager.sessions) != 1 || len(store.values) != 1 {
|
||||
t.Fatalf("failed expired cleanup should remain retryable: sessions=%d values=%d", len(manager.sessions), len(store.values))
|
||||
}
|
||||
|
||||
store.failDelete = false
|
||||
if err := manager.Check(result.VerificationToken, "cloudflare", result.CredentialRef, "example.com"); err == nil {
|
||||
t.Fatal("expired DNS verification token should remain rejected after cleanup retry")
|
||||
}
|
||||
if len(manager.sessions) != 0 || len(store.values) != 0 {
|
||||
t.Fatalf("expired cleanup retry left temporary state: sessions=%d values=%d", len(manager.sessions), len(store.values))
|
||||
}
|
||||
}
|
||||
|
||||
@ -3769,3 +3769,14 @@ Linux Ubuntu 22.04 上的 `Scert-admin` 手动启停、无开机自启动、无
|
||||
- [~] 进行中:DNS 过期清理与初始化回滚矩阵、其他 P0-10 任务链、跨数据库实机验证和统一复审。
|
||||
- [ ] 未开始:P0-12、真实数据库清理初始化、密码 SSH、远端多服务器部署及最终生产门禁。
|
||||
- [!] 不得标记 `[✓]`,总体验收仍为 **P0-03:完成,待复审**。
|
||||
|
||||
## 本轮开发进度(2026-09-30,P0-10 DNS 过期凭据清理重试)
|
||||
|
||||
总体验收继续保持 **P0-03:完成,待复审**,本轮继续先开发、后统一测试。
|
||||
|
||||
- [x] 已完成,等待复审:过期 DNS 验证会话清理失败时恢复原会话,后续操作可重试删除临时凭据。
|
||||
- [x] 已完成,等待复审:清理重试成功后仍拒绝过期令牌,避免凭据回收修复引入授权回退。
|
||||
- [x] 已完成:新增过期清理失败回归测试;后端全量测试、静态检查和差异检查均通过。
|
||||
- [~] 进行中:DNS 初始化回滚矩阵、令牌生命周期、其他 P0-10 任务链、跨数据库实机验证和统一复审。
|
||||
- [ ] 未开始:P0-12、真实数据库清理初始化、密码 SSH、远端多服务器部署及最终生产门禁。
|
||||
- [!] 不得标记 `[✓]`,总体验收仍为 **P0-03:完成,待复审**。
|
||||
|
||||
@ -1074,3 +1074,14 @@ P0-05 从 `[!]` 调整为 `[~]`,并未标记完成;P0-04 剩余门禁和全
|
||||
- [~] 继续执行:DNS 过期清理、初始化回滚、令牌生命周期、任务链、三数据库实机矩阵和统一复审。
|
||||
- [ ] 尚未执行:P0-12、真实生产数据库清理、密码 SSH、远端部署、真实 HTTPS/DNS/ACME 和最终生产门禁。
|
||||
- [!] 不新增 `[✓]`;总体验收继续为 **P0-03:完成,待复审**。
|
||||
|
||||
## 本轮执行记录(2026-09-30,P0-10 DNS 过期凭据清理重试)
|
||||
|
||||
本轮按“先开发、后统一测试”执行,总体验收仍保持 **P0-03:完成,待复审**。
|
||||
|
||||
- [x] 过期 DNS 会话删除临时凭据失败时恢复原会话,避免清理失败造成不可重试残留。
|
||||
- [x] 清理重试成功后令牌仍不可用,验证会话不会因补偿清理重新获得授权。
|
||||
- [x] `go test ./...`、`go vet ./...`、`git diff --check` 已通过;本轮仅完成本地开发与自动化回归。
|
||||
- [~] 继续执行:DNS 初始化回滚、令牌消费、任务链、三数据库实机矩阵和统一复审。
|
||||
- [ ] 尚未执行:P0-12、真实生产数据库清理、密码 SSH、远端部署、真实 HTTPS/DNS/ACME 和最终生产门禁。
|
||||
- [!] 不新增 `[✓]`;总体验收继续为 **P0-03:完成,待复审**。
|
||||
|
||||
@ -1722,3 +1722,14 @@ P0-11 生产验收门禁
|
||||
- [~] 进行中:DNS 过期清理、初始化回滚、令牌消费、其他 P0-10 任务链、跨数据库实机矩阵和统一复审。
|
||||
- [ ] 未开始:P0-12 备份恢复、密码 SSH、远端部署、真实 HTTPS/DNS/ACME 和最终生产门禁。
|
||||
- [!] 总体验收不得升级:仍为 **P0-03:完成,待复审**,本轮没有新增 `[✓]` 复审通过项。
|
||||
|
||||
## 本轮开发进度(2026-09-30,P0-10 DNS 过期凭据清理重试)
|
||||
|
||||
总体验收位置继续保持 **P0-03:完成,待复审**;本轮仅补齐 DNS 验证令牌过期后的临时凭据清理重试,不代表 P0-10 统一复审通过或生产部署验收通过。
|
||||
|
||||
- [x] 已完成,等待复审:过期验证会话对应的临时凭据删除失败时恢复原会话,保留后续清理入口,不再静默丢失。
|
||||
- [x] 已完成,等待复审:清理重试成功后过期会话和临时凭据同时移除,令牌仍保持过期不可用。
|
||||
- [x] 已完成:新增过期清理失败与重试回归测试;后端 `go test ./...`、`go vet ./...` 和 `git diff --check` 通过。
|
||||
- [~] 进行中:DNS 初始化回滚、令牌消费、其他 P0-10 任务链、跨数据库实机矩阵和统一复审。
|
||||
- [ ] 未开始:P0-12 备份恢复、密码 SSH、远端部署、真实 HTTPS/DNS/ACME 和最终生产门禁。
|
||||
- [!] 总体验收不得升级:仍为 **P0-03:完成,待复审**,本轮没有新增 `[✓]` 复审通过项。
|
||||
|
||||
@ -647,3 +647,13 @@ P0-06 与 P0-07 均维持 `[~]`,六阶段中的隔离、生产和最终验收
|
||||
- [x] 已新增对应回归测试,并通过后端全量测试、静态检查和差异检查。
|
||||
- [~] DNS 过期清理、初始化回滚、令牌消费及 P0-10 其他任务链继续开发和统一复审。
|
||||
- [!] 本地自动化结果不得替代真实 DNS/ACME、密码 SSH、远端多服务器部署和最终生产门禁;不得新增 `[✓]`。
|
||||
|
||||
## 本轮规格执行记录(2026-09-30,P0-10 DNS 过期凭据清理重试)
|
||||
|
||||
本轮仅补齐 DNS 验证令牌过期后的临时凭据清理重试要求,总体验收继续保持 **P0-03:完成,待复审**。
|
||||
|
||||
- [x] 过期会话临时凭据清理失败时必须恢复原会话,后续操作必须能够再次触发清理。
|
||||
- [x] 补偿清理成功后必须保持令牌过期,不得因恢复清理状态而重新授权验证结果。
|
||||
- [x] 已新增对应回归测试,并通过后端全量测试、静态检查和差异检查。
|
||||
- [~] DNS 初始化回滚、令牌消费及 P0-10 其他任务链继续开发和统一复审。
|
||||
- [!] 本地自动化结果不得替代真实 DNS/ACME、密码 SSH、远端多服务器部署和最终生产门禁;不得新增 `[✓]`。
|
||||
|
||||
Loading…
Reference in New Issue
Block a user