feat(v1.1.8): F1 drop plaintext realtime_signing_key (migration 0032)

v1.1.7 added at-rest encryption for app_secrets.realtime_signing_key
plus a startup task that backfilled encryption over the plaintext
column. v1.1.7's CHANGELOG committed v1.1.8 to dropping the
plaintext column; this commit follows through.

Migration 0032:
  * Guard query: refuses to apply if any row still has
    realtime_signing_key IS NOT NULL but realtime_signing_key_encrypted
    IS NULL. Forces operators who skipped v1.1.7 to apply it first.
  * ALTER TABLE app_secrets DROP COLUMN IF EXISTS
    realtime_signing_key.

app_secrets_repo:
  * decode_signing_key now reads encrypted+nonce only; the plaintext
    fallback is gone. (The schema still allows it via DROP IF EXISTS
    semantics on replay; once dropped, the column doesn't exist —
    the SELECT no longer requests it.)
  * Removed migrate_plaintext_keys (the v1.1.7 startup sweep).
  * Tests for the falls-back-to-plaintext path are gone with it; the
    remaining tests cover the encrypted-only happy path, the
    missing-columns None case, and the wrong-master-key Crypto error.

picloud/lib.rs: removed the migrate_plaintext_keys startup call
+ replaced with a comment explaining the upgrade-path requirement.

LOAD-BEARING: v1.1.8 requires v1.1.7 to have been applied first.
Operators upgrading directly from v1.1.6 or earlier must apply
v1.1.7 (which performs the encryption pass) before applying v1.1.8.
This is enforced both by the migration guard and by the CHANGELOG
(in a later commit).

Brief mentioned dropping a "realtime_signing_key_nonce_LEGACY_IF_EXISTS"
column — recon confirmed migration 0025 only added the plaintext
column + the encrypted/nonce pair, so no legacy nonce column exists
to drop. Documented in HANDBACK §7.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-06 14:56:27 +02:00
parent 6449cb6f6a
commit 3c2c4a3767
3 changed files with 64 additions and 108 deletions

View File

@@ -196,21 +196,11 @@ pub async fn build_app(
pool.clone(),
master_key.clone(),
));
// v1.1.7 two-phase migration: encrypt any plaintext realtime signing
// keys at rest. Idempotent — only touches rows not yet encrypted. The
// plaintext column is dropped in v1.1.8.
match app_secrets_repo.migrate_plaintext_keys().await {
Ok(0) => {}
Ok(n) => {
tracing::info!(
migrated = n,
"encrypted plaintext realtime signing keys at rest"
);
}
Err(e) => {
tracing::error!(error = %e, "failed to encrypt realtime signing keys (continuing)");
}
}
// v1.1.7's migrate_plaintext_keys startup sweep was deleted in
// v1.1.8 F1 alongside migration 0032, which drops the plaintext
// column. Operators upgrading from v1.1.6 or earlier MUST apply
// v1.1.7 first; the 0032 migration's guard refuses to run
// otherwise.
let realtime_authority: Arc<dyn RealtimeAuthority> = Arc::new(RealtimeAuthorityImpl::new(
topic_repo.clone(),
app_secrets_repo.clone(),