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.
|
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.
|
**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.
|
||||||
|
|||||||
+3
-2
@@ -335,6 +335,7 @@ DEV-01…04 are **pre-existing or cost-driven** and none is introduced by this f
|
|||||||
| ASM-05 | The existing Umami instance at `analytics.slpsoftware.nl` remains available and its script origin can be added to the CSP. | FR-16, FR-18. | Umami setup gains host work, as in the reference project. |
|
| ASM-05 | The existing Umami instance at `analytics.slpsoftware.nl` remains available and its script origin can be added to the CSP. | FR-16, FR-18. | Umami setup gains host work, as in the reference project. |
|
||||||
| ASM-06 | One Sentry project with environment tags is acceptable for both backend and frontend events of this CMS. | D-19. | Split into more projects; only configuration changes. |
|
| ASM-06 | One Sentry project with environment tags is acceptable for both backend and frontend events of this CMS. | D-19. | Split into more projects; only configuration changes. |
|
||||||
| ASM-07 | Existing Gitea secrets for the Pi (`PI_MAIN_*` in the reference project) can be reused or replicated for this repository. | FR-02. | New secrets are created; documented in FR-23. |
|
| ASM-07 | Existing Gitea secrets for the Pi (`PI_MAIN_*` in the reference project) can be reused or replicated for this repository. | FR-02. | New secrets are created; documented in FR-23. |
|
||||||
|
| ASM-08 | **No environment holds API keys encrypted under the old file-based key ring.** Confirmed by the user on 2026-07-28: the CMS is not deployed anywhere yet, so the first deploy of U2 meets an empty database. U2 therefore ships **no** one-time re-encryption path for pre-existing ciphertext. | U2 moved the Data Protection key ring from the filesystem into the database. Ciphertext written under the old ring cannot be decrypted by the new one — it fails with `The key {id} was not found in the key ring`. Observed locally on 2026-07-28 against rows written before U2, and resolved by deleting and re-registering. | **U6 must not deploy** until either the affected rows are cleared and the instances re-registered, or U2 gains a migration that reads the old ring as a fallback and re-encrypts. This is a silent, destructive failure of master/slave communication — exactly the class NFR-02 exists to prevent. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -353,9 +354,9 @@ DEV-01…04 are **pre-existing or cost-driven** and none is introduced by this f
|
|||||||
|
|
||||||
This feature turns a manually deployed modular-monolith CMS into one with an automated, auditable pipeline, on hosting where nothing can be configured server-side.
|
This feature turns a manually deployed modular-monolith CMS into one with an automated, auditable pipeline, on hosting where nothing can be configured server-side.
|
||||||
|
|
||||||
**24 functional requirements, 10 non-functional requirements, 32 traced decisions, 7 assumptions, 2 remaining open items, 4 documented security deviations.**
|
**24 functional requirements, 10 non-functional requirements, 32 traced decisions, 8 assumptions, 2 remaining open items, 4 documented security deviations.**
|
||||||
|
|
||||||
*(FR-24 added and OPEN-02 closed at Application Design on 2026-07-27. OPEN-01 closed at NFR Design on 2026-07-28.)*
|
*(FR-24 added and OPEN-02 closed at Application Design on 2026-07-27. OPEN-01 closed at NFR Design on 2026-07-28. ASM-08 added on 2026-07-28 after the key-ring migration gap surfaced in local testing.)*
|
||||||
|
|
||||||
The three requirements that carry the most risk if implemented carelessly:
|
The three requirements that carry the most risk if implemented carelessly:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user