Records that the key-ring move leaves older ciphertext unreadable
Starting both hosts after U2 fails to decrypt a stored slave API key: the row was encrypted under the old file-based key ring, and the database key ring holds a different, freshly generated key. The key ring working correctly is exactly why the old key is not found. U2 moved the ring without a path for ciphertext that predates it. Confirmed with the user that nothing is deployed yet, so no migration ships and the first deploy meets an empty database. ASM-08 records the condition and makes it blocking for U6 if it ever stops holding. Also notes that the master lets the CryptographicException escape while the slave catches it and reports "master API key rejected" instead — the ambiguity U4's event was designed around, showing up on day one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HHoJpxYXzHACSQguHrC5fw
This commit is contained in:
+32
@@ -85,3 +85,35 @@ Also recorded for Operations: **DEV-05** — keys are stored unencrypted at rest
|
||||
No failures occurred during generation.
|
||||
|
||||
**Not verifiable at this stage**: the composed startup path (`MigrateCoreDatabase` against a real database, and both hosts actually starting) requires SQL Server. Carried to the phase-level Build and Test stage.
|
||||
|
||||
---
|
||||
|
||||
## Follow-up found in local testing (2026-07-28)
|
||||
|
||||
Starting both hosts after U2 produced, on the master:
|
||||
|
||||
```
|
||||
CryptographicException: The key {2ad1bf17-...} was not found in the key ring
|
||||
at ApiKeyProtector.Unprotect
|
||||
at CmsInstanceService.VerifyIntegrityAsync
|
||||
```
|
||||
|
||||
**Not a defect in this unit — it is this unit's change meeting data written before it.** The stored
|
||||
`MasterCmsInstances.ApiKey` had been encrypted under the old file-based key ring in
|
||||
`%LOCALAPPDATA%\ASP.NET\DataProtection-Keys`; both databases now hold their own freshly generated
|
||||
single key, which cannot decrypt it. The key ring working correctly is precisely why the old key is
|
||||
not found.
|
||||
|
||||
**The gap this unit left**: no one-time path for ciphertext that predates the move. Recorded as
|
||||
**ASM-08** in `requirements.md`. Confirmed with the user on 2026-07-28 that the CMS is not deployed
|
||||
anywhere yet, so the first deploy meets an empty database and no migration is shipped. If that ever
|
||||
stops being true, U6 must not deploy until either the affected rows are cleared and the instances
|
||||
re-registered, or this unit gains a fallback that reads the old ring and re-encrypts.
|
||||
|
||||
Resolved locally by deleting the one stale row on each side and re-registering the slave.
|
||||
|
||||
**Worth noting for U4's alerting**: the two sides fail differently. `Modules.Master/ApiKeyProtector`
|
||||
lets the exception escape, while `Modules.Availability/MasterApiKeyProtector` catches it and returns
|
||||
null, so the slave reports "master API key rejected" instead. That is exactly the ambiguity U4's
|
||||
`MasterApiKeyRejected` event documents — an intruder, **or** an unreadable key ring — occurring in
|
||||
practice.
|
||||
|
||||
Reference in New Issue
Block a user