保护初始化完成后的正式凭据

This commit is contained in:
xuans 2026-09-16 18:28:50 +08:00
parent c830bcebfa
commit 9a68fb80d9
4 changed files with 105 additions and 4 deletions

View File

@ -248,22 +248,39 @@ func (m *DatabaseVerificationManager) resolveSession(token string) (databaseVeri
return session, nil
}
// DiscardAll 回收所有尚未消费的数据库验证令牌及其临时凭据。
func (m *DatabaseVerificationManager) DiscardAll() {
m.DiscardAllExcept()
}
// DiscardAllExcept 回收全部尚未消费的数据库验证令牌,但保留已经成为正式配置依赖的凭据。
func (m *DatabaseVerificationManager) DiscardAllExcept(protectedReferences ...string) {
if m == nil {
return
}
protected := protectedCredentialReferences(protectedReferences)
m.mu.Lock()
references := make([]string, 0, len(m.sessions))
for token, session := range m.sessions {
delete(m.sessions, token)
if session.CredentialRef != "" {
references = append(references, session.CredentialRef)
if reference := strings.TrimSpace(session.CredentialRef); reference != "" && !protected[reference] {
references = append(references, reference)
}
}
m.mu.Unlock()
m.deleteCredentials(references)
}
func protectedCredentialReferences(references []string) map[string]bool {
protected := make(map[string]bool, len(references))
for _, reference := range references {
if reference = strings.TrimSpace(reference); reference != "" {
protected[reference] = true
}
}
return protected
}
func (m *DatabaseVerificationManager) reserve() error {
now := m.now().UTC()
m.mu.Lock()

View File

@ -200,6 +200,44 @@ func TestDatabaseVerificationDiscardOnlyRemovesSelectedToken(t *testing.T) {
}
}
func TestDatabaseVerificationDiscardAllExceptProtectsFormalCredential(t *testing.T) {
base := config.Default(t.TempDir())
store := &memoryDatabaseStore{}
manager := NewDatabaseVerificationManager(base, store)
manager.verify = func(context.Context, config.DatabaseConfig) error { return nil }
protected, err := manager.Verify(t.Context(), DatabaseInput{
Driver: "mysql", Host: "db.example.test", Database: "scert",
Username: "formal_user", Password: "formal-password", TLSMode: "true",
})
if err != nil {
t.Fatal(err)
}
pending, err := manager.Verify(t.Context(), DatabaseInput{
Driver: "postgres", Host: "db.example.test", Database: "scert",
Username: "pending_user", Password: "pending-password", TLSMode: "verify-full",
})
if err != nil {
t.Fatal(err)
}
manager.DiscardAllExcept(" " + protected.CredentialRef + " ")
if _, err := manager.Resolve(protected.VerificationToken); err == nil {
t.Fatal("批量清理后受保护凭据对应的验证令牌仍然有效")
}
if _, err := manager.Resolve(pending.VerificationToken); err == nil {
t.Fatal("批量清理后待处理凭据对应的验证令牌仍然有效")
}
store.mu.Lock()
_, protectedExists := store.values[protected.CredentialRef]
_, pendingExists := store.values[pending.CredentialRef]
store.mu.Unlock()
if !protectedExists || pendingExists {
t.Fatalf("数据库凭据保护范围错误:protected=%v pending=%v", protectedExists, pendingExists)
}
}
func TestDatabaseVerificationResolveIsServerOnlyAndDoesNotConsumeToken(t *testing.T) {
base := config.Default(t.TempDir())
store := &memoryDatabaseStore{}

View File

@ -225,15 +225,26 @@ func (m *DNSVerificationManager) Discard(token string) error {
// DiscardAll 回收所有尚未消费的 DNS 验证令牌及其临时凭据。
func (m *DNSVerificationManager) DiscardAll() {
m.DiscardAllExcept()
}
// DiscardAllExcept 回收全部尚未消费的 DNS 验证令牌,但保留已经成为正式配置依赖的凭据。
func (m *DNSVerificationManager) DiscardAllExcept(protectedReferences ...string) {
if m == nil {
return
}
protected := make(map[string]bool, len(protectedReferences))
for _, reference := range protectedReferences {
if reference = strings.TrimSpace(reference); reference != "" {
protected[reference] = true
}
}
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)
if reference := strings.TrimSpace(session.CredentialRef); reference != "" && !protected[reference] {
references = append(references, reference)
}
}
m.mu.Unlock()

View File

@ -191,6 +191,41 @@ func TestDNSVerificationDiscardAllRemovesPendingCredentials(t *testing.T) {
}
}
func TestDNSVerificationDiscardAllExceptProtectsFormalCredential(t *testing.T) {
store := &memorySecretWriter{}
manager := NewDNSVerificationManager(fakeDNSTester{}, store)
protected, err := manager.Verify(t.Context(), DNSVerificationInput{
ProviderID: "aliyun", TestDomain: "formal.example.com",
Credentials: map[string]string{"access_key_id": "formal-id", "access_key_secret": "formal-secret"},
})
if err != nil {
t.Fatal(err)
}
pending, err := manager.Verify(t.Context(), DNSVerificationInput{
ProviderID: "aliyun", TestDomain: "pending.example.com",
Credentials: map[string]string{"access_key_id": "pending-id", "access_key_secret": "pending-secret"},
})
if err != nil {
t.Fatal(err)
}
manager.DiscardAllExcept(" " + protected.CredentialRef + " ")
if _, err := manager.Resolve(protected.VerificationToken); err == nil {
t.Fatal("批量清理后受保护凭据对应的验证令牌仍然有效")
}
if _, err := manager.Resolve(pending.VerificationToken); err == nil {
t.Fatal("批量清理后待处理凭据对应的验证令牌仍然有效")
}
store.mu.Lock()
_, protectedExists := store.values[protected.CredentialRef]
_, pendingExists := store.values[pending.CredentialRef]
store.mu.Unlock()
if !protectedExists || pendingExists {
t.Fatalf("DNS 凭据保护范围错误:protected=%v pending=%v", protectedExists, pendingExists)
}
}
func TestDNSVerificationManagerRejectsExpiredToken(t *testing.T) {
store := &memorySecretWriter{}
manager := NewDNSVerificationManager(fakeDNSTester{}, store)