Skip to content

fix: fail closed when the key ring would be stored unencrypted#63

Merged
andregoepel merged 2 commits into
mainfrom
bugfix/require-keyring-encryption
Jul 4, 2026
Merged

fix: fail closed when the key ring would be stored unencrypted#63
andregoepel merged 2 commits into
mainfrom
bugfix/require-keyring-encryption

Conversation

@andregoepel

Copy link
Copy Markdown
Owner

Closes #54 (F5 — DataProtection key-ring encryption optional and co-located with protected data).

What

UseAppFoundation now refuses to start outside Development when the DataProtection key ring would be persisted without at-rest encryption. The check inspects the resolved KeyManagementOptions.XmlEncryptor: it is non-null whenever encryption is configured — certificate (DataProtection:CertificatePath), Azure Key Vault, KMS, etc. — so it reflects the actual end state regardless of how protection was wired, not just whether the foundation's own cert path was set.

  • Secure by default: no encryptor + non-Development ⇒ InvalidOperationException with an actionable message.
  • AppFoundationOptions.AllowUnprotectedKeyRing opt-out for hosts whose database storage is encrypted at rest by other means.
  • Development is always exempt (local convenience; ASP.NET already logs its at-rest warning there).

Logic extracted to an internal EnsureKeyRingProtected(isDevelopment, allowUnprotectedKeyRing, xmlEncryptor) helper and unit-tested.

Why

The key ring lives in the same Postgres as the data it protects. With encryption optional, a production deployment that forgot the certificate stored the keys in cleartext — so a single DB dump/backup would decrypt the SMTP password, the /login handoff tokens, and any auth cookies issued with this key ring.

⚠️ Upgrade note (behavior change)

A host running in a non-Development environment must now configure key encryption or it will fail to start:

  • set DataProtection:CertificatePath (+ DataProtection:CertificatePassword), or
  • configure Key Vault/KMS via AppFoundationOptions.ConfigureDataProtection, or
  • explicitly set AppFoundationOptions.AllowUnprotectedKeyRing = true to keep the old behavior.

The exception message states these options.

Tests

EnsureKeyRingProtectedTests: throws when non-Dev + no encryptor + not allowed; does not throw when an encryptor is present, when explicitly allowed, or in Development. Existing AddAppFoundationDataProtectionTests remain green (they exercise registration, not UseAppFoundation).

Verification

  • dotnet build -c Release — 0 errors.
  • dotnet test -c Release — 70/70 passing (4 new).
  • Rebased on latest main.

The DataProtection key ring is persisted in the same Postgres as the data
it protects, but at-rest encryption was optional — so a non-Development
deployment without a configured certificate stored the keys in cleartext,
and a DB dump could decrypt the SMTP password, login tokens, and auth
cookies.

UseAppFoundation now refuses to start outside Development when the
resolved KeyManagementOptions.XmlEncryptor is null (i.e. no cert / Key
Vault / KMS configured). Checking the resolved encryptor reflects the
actual end state regardless of how protection was wired. A host that
accepts an unencrypted key ring (e.g. storage encrypted by other means)
opts in via AppFoundationOptions.AllowUnprotectedKeyRing.

Closes #54
Add a Production configuration section covering the settings a
non-Development host must provide now that the foundation fails fast on
unsafe defaults: data-protection key encryption (cert / Key Vault /
AllowUnprotectedKeyRing), reverse-proxy trust (KnownProxyNetworks via
env, with an nginx + Docker example), and the schema-creation mode.
Expand the AppFoundationOptions table with the newer options.
@andregoepel
andregoepel merged commit 10229c1 into main Jul 4, 2026
3 checks passed
@andregoepel
andregoepel deleted the bugfix/require-keyring-encryption branch July 5, 2026 07:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Security] DataProtection key ring encryption optional and co-located with protected data (F5)

1 participant