补充运行时密文校验与迁移取消处理
Some checks are pending
Security Checks / security (push) Waiting to run

This commit is contained in:
smail 2026-08-30 21:48:29 +08:00
parent 3f6536bc87
commit 6de9fb1ebc
4 changed files with 77 additions and 1 deletions

View File

@ -173,6 +173,9 @@ func newConfigManager(root string, options ConfigLoaderOptions, security ConfigS
if err != nil {
return nil, err
}
if err := ValidateRuntimeSecrets(vault, runtime); err != nil {
return nil, err
}
snapshots, err := NewSnapshotStore(runtime, options.Validation)
if err != nil {
return nil, err
@ -204,6 +207,9 @@ func (m *ConfigManager) Load(ctx context.Context) error {
if err != nil {
return err
}
if err := ValidateRuntimeSecrets(m.vault, runtime); err != nil {
return err
}
if err := m.snapshots.Publish(runtime); err != nil {
return err
}

View File

@ -139,6 +139,55 @@ func TestConfigLoaderRejectsMissingPartitionAndGenerationMismatch(t *testing.T)
}
}
func TestConfigManagerRejectsUnavailableRuntimeSecretsAtStartup(t *testing.T) {
root := t.TempDir()
runtime := validMySQLRuntimeConfig(t, root)
otherVault, err := credential.NewVault(t.TempDir())
if err != nil {
t.Fatal(err)
}
foreignCiphertext, err := otherVault.Encrypt("foreign-database-secret")
if err != nil {
t.Fatal(err)
}
runtime.Root.Database.Password.Ciphertext = foreignCiphertext
writeRuntimeFixture(t, root, runtime)
_, err = NewConfigManager(root, ConfigLoaderOptions{Validation: RuntimeValidationOptions{HasSuperAdmin: true, Production: true}})
if !errors.Is(err, ErrSecretUnavailable) {
t.Fatalf("manager construction error = %v, want unavailable secret", err)
}
}
func TestConfigManagerLoadRejectsUnavailableRuntimeSecretsWithoutChangingSnapshot(t *testing.T) {
root := t.TempDir()
options := ConfigLoaderOptions{Validation: RuntimeValidationOptions{HasSuperAdmin: true, Production: true}}
writeRuntimeFixture(t, root, validMySQLRuntimeConfig(t, root))
manager, err := NewConfigManager(root, options)
if err != nil {
t.Fatal(err)
}
current := manager.Snapshot()
otherVault, err := credential.NewVault(t.TempDir())
if err != nil {
t.Fatal(err)
}
foreignCiphertext, err := otherVault.Encrypt("foreign-database-secret")
if err != nil {
t.Fatal(err)
}
current.Root.Database.Password.Ciphertext = foreignCiphertext
writeRuntimeFixture(t, root, current)
if err := manager.Load(context.Background()); !errors.Is(err, ErrSecretUnavailable) {
t.Fatalf("load error = %v, want unavailable secret", err)
}
loaded := manager.Snapshot()
if loaded.Root.Database.Password.Ciphertext == foreignCiphertext {
t.Fatal("rejected load changed the active snapshot")
}
}
func TestConfigManagerApplyPreflightsSnapshotBeforePersisting(t *testing.T) {
root := t.TempDir()
writeRuntimeFixture(t, root, validRuntimeConfig())

View File

@ -116,7 +116,10 @@ func Current() *Service {
return current.value
}
func (s *Service) Preview(_ context.Context, source config.DatabaseConfig, currentGeneration uint64, targetName string) (Record, string, error) {
func (s *Service) Preview(ctx context.Context, source config.DatabaseConfig, currentGeneration uint64, targetName string) (Record, string, error) {
if err := contextError(ctx); err != nil {
return Record{}, "", err
}
if s == nil || currentGeneration == 0 {
return Record{}, "", ErrInvalidRequest
}
@ -139,6 +142,9 @@ func (s *Service) Preview(_ context.Context, source config.DatabaseConfig, curre
if err != nil {
return Record{}, "", ErrInvalidRequest
}
if err := contextError(ctx); err != nil {
return Record{}, "", err
}
now := s.currentTime()
item := &record{Record: Record{ID: id, Status: StatusPreviewed, Source: plan.Source, Target: plan.Target, CurrentGeneration: currentGeneration, CreatedAt: now, UpdatedAt: now}, tokenHash: sha256.Sum256([]byte(token)), expiresAt: now.Add(previewLifetime), sourceConfig: plan.sourceConfig, targetConfig: plan.targetConfig}
s.mu.Lock()

View File

@ -81,6 +81,21 @@ func TestPreviewRejectsUnsafeTargetName(t *testing.T) {
}
}
func TestPreviewHonorsCanceledContextWithoutCreatingRecord(t *testing.T) {
service := NewService(testRunner{})
ctx, cancel := context.WithCancel(context.Background())
cancel()
if _, _, err := service.Preview(ctx, testDatabase(t), 1, "smail_next"); !errors.Is(err, context.Canceled) {
t.Fatalf("canceled preview returned %v, want context canceled", err)
}
service.mu.RLock()
defer service.mu.RUnlock()
if len(service.records) != 0 {
t.Fatalf("canceled preview created %d records", len(service.records))
}
}
func TestFailedExecutionKeepsSafeState(t *testing.T) {
service := NewService(nil)
record, token, err := service.Preview(context.Background(), testDatabase(t), 1, "smail_next")