完善初始化验证凭据精确回收

This commit is contained in:
xuans 2026-09-16 18:06:17 +08:00
parent 3283461621
commit aa52981079
4 changed files with 179 additions and 0 deletions

View File

@ -150,6 +150,33 @@ func (m *DatabaseVerificationManager) Consume(token, credentialRef string) (conf
return m.session(token, credentialRef, true)
}
// Discard 按验证令牌精确回收尚未消费的数据库凭据,不影响其他初始化会话。
func (m *DatabaseVerificationManager) Discard(token string) error {
if m == nil {
return errors.New("数据库验证服务尚未初始化")
}
token = strings.TrimSpace(token)
if token == "" {
return errors.New("数据库验证令牌不能为空")
}
now := m.now().UTC()
m.mu.Lock()
expired := m.clearExpiredLocked(now)
session, ok := m.sessions[token]
if ok {
delete(m.sessions, token)
}
m.mu.Unlock()
m.deleteCredentials(expired)
if !ok {
return errors.New("数据库验证令牌无效或已经过期")
}
if err := m.deleteCredential(session.CredentialRef); err != nil {
return errors.New("数据库临时凭据回收失败")
}
return nil
}
func (m *DatabaseVerificationManager) session(token, credentialRef string, consume bool) (config.DatabaseConfig, error) {
if m == nil {
return config.DatabaseConfig{}, errors.New("数据库验证服务尚未初始化")

View File

@ -157,3 +157,44 @@ func TestSQLiteRejectsUserControlledPathAndExternalFields(t *testing.T) {
t.Fatal("SQLite 用户自定义路径未被拒绝")
}
}
func TestDatabaseVerificationDiscardOnlyRemovesSelectedToken(t *testing.T) {
base := config.Default(t.TempDir())
store := &memoryDatabaseStore{}
manager := NewDatabaseVerificationManager(base, store)
manager.verify = func(context.Context, config.DatabaseConfig) error { return nil }
first, err := manager.Verify(t.Context(), DatabaseInput{
Driver: "mysql", Host: "db.example.test", Database: "scert",
Username: "first_user", Password: "first-password", TLSMode: "true",
})
if err != nil {
t.Fatal(err)
}
second, err := manager.Verify(t.Context(), DatabaseInput{
Driver: "postgres", Host: "db.example.test", Database: "scert",
Username: "second_user", Password: "second-password", TLSMode: "verify-full",
})
if err != nil {
t.Fatal(err)
}
if err := manager.Discard(first.VerificationToken); err != nil {
t.Fatalf("精确回收数据库验证令牌失败:%v", err)
}
if _, err := manager.Check(first.VerificationToken, first.CredentialRef); err == nil {
t.Fatal("已回收的数据库验证令牌仍然有效")
}
if _, err := manager.Check(second.VerificationToken, second.CredentialRef); err != nil {
t.Fatalf("精确回收影响了其他数据库验证令牌:%v", err)
}
store.mu.Lock()
_, firstExists := store.values[first.CredentialRef]
_, secondExists := store.values[second.CredentialRef]
store.mu.Unlock()
if firstExists || !secondExists {
t.Fatalf("数据库凭据回收范围错误:first=%v second=%v", firstExists, secondExists)
}
if err := manager.Discard(first.VerificationToken); err == nil {
t.Fatal("数据库验证令牌被重复回收")
}
}

View File

@ -172,6 +172,51 @@ func (m *DNSVerificationManager) Consume(token, providerID, credentialRef, testD
return m.validateSession(token, providerID, credentialRef, testDomain, true)
}
// Discard 按验证令牌精确回收尚未消费的 DNS 临时凭据,不影响其他初始化会话。
func (m *DNSVerificationManager) Discard(token string) error {
if m == nil {
return errors.New("DNS 在线验证服务未初始化")
}
token = strings.TrimSpace(token)
if token == "" {
return errors.New("DNS 验证令牌不能为空")
}
now := m.now().UTC()
m.mu.Lock()
expiredRefs := m.clearExpiredLocked(now)
session, ok := m.sessions[token]
if ok {
delete(m.sessions, token)
}
m.mu.Unlock()
m.deleteCredentials(expiredRefs)
if !ok {
return errors.New("DNS 验证令牌无效或已经过期")
}
if err := m.deleteCredential(session.CredentialRef); err != nil {
m.logCleanupFailure()
return errors.New("DNS 临时凭据回收失败")
}
return nil
}
// DiscardAll 回收所有尚未消费的 DNS 验证令牌及其临时凭据。
func (m *DNSVerificationManager) DiscardAll() {
if m == nil {
return
}
m.mu.Lock()
references := make([]string, 0, len(m.sessions))
for token, session := range m.sessions {
delete(m.sessions, token)
if strings.TrimSpace(session.CredentialRef) != "" {
references = append(references, session.CredentialRef)
}
}
m.mu.Unlock()
m.deleteCredentials(references)
}
func (m *DNSVerificationManager) validateSession(token, providerID, credentialRef, testDomain string, consume bool) error {
if m == nil {
return errors.New("DNS 在线验证服务未初始化")

View File

@ -123,6 +123,72 @@ func TestDNSVerificationManagerRejectsMismatchedConfiguration(t *testing.T) {
}
}
func TestDNSVerificationDiscardOnlyRemovesSelectedToken(t *testing.T) {
store := &memorySecretWriter{}
manager := NewDNSVerificationManager(fakeDNSTester{}, store)
first, err := manager.Verify(t.Context(), DNSVerificationInput{
ProviderID: "aliyun", TestDomain: "first.example.com",
Credentials: map[string]string{"access_key_id": "first-id", "access_key_secret": "first-secret"},
})
if err != nil {
t.Fatal(err)
}
second, err := manager.Verify(t.Context(), DNSVerificationInput{
ProviderID: "aliyun", TestDomain: "second.example.com",
Credentials: map[string]string{"access_key_id": "second-id", "access_key_secret": "second-secret"},
})
if err != nil {
t.Fatal(err)
}
if err := manager.Discard(first.VerificationToken); err != nil {
t.Fatalf("精确回收 DNS 验证令牌失败:%v", err)
}
if err := manager.Check(first.VerificationToken, "aliyun", first.CredentialRef, "first.example.com"); err == nil {
t.Fatal("已回收的 DNS 验证令牌仍然有效")
}
if err := manager.Check(second.VerificationToken, "aliyun", second.CredentialRef, "second.example.com"); err != nil {
t.Fatalf("精确回收影响了其他 DNS 验证令牌:%v", err)
}
store.mu.Lock()
_, firstExists := store.values[first.CredentialRef]
_, secondExists := store.values[second.CredentialRef]
store.mu.Unlock()
if firstExists || !secondExists {
t.Fatalf("DNS 凭据回收范围错误:first=%v second=%v", firstExists, secondExists)
}
if err := manager.Discard(first.VerificationToken); err == nil {
t.Fatal("DNS 验证令牌被重复回收")
}
}
func TestDNSVerificationDiscardAllRemovesPendingCredentials(t *testing.T) {
store := &memorySecretWriter{}
manager := NewDNSVerificationManager(fakeDNSTester{}, store)
results := make([]DNSVerificationResult, 0, 2)
for _, testDomain := range []string{"first.example.com", "second.example.com"} {
result, err := manager.Verify(t.Context(), DNSVerificationInput{
ProviderID: "aliyun", TestDomain: testDomain,
Credentials: map[string]string{"access_key_id": "test-id", "access_key_secret": "test-secret"},
})
if err != nil {
t.Fatal(err)
}
results = append(results, result)
}
manager.DiscardAll()
for _, result := range results {
if err := manager.Check(result.VerificationToken, "aliyun", result.CredentialRef, "first.example.com"); err == nil {
t.Fatal("DiscardAll 后 DNS 验证令牌仍然有效")
}
}
store.mu.Lock()
remaining := len(store.values)
store.mu.Unlock()
if remaining != 0 {
t.Fatalf("DiscardAll 后仍有 DNS 临时凭据:%d", remaining)
}
}
func TestDNSVerificationManagerRejectsExpiredToken(t *testing.T) {
store := &memorySecretWriter{}
manager := NewDNSVerificationManager(fakeDNSTester{}, store)