Skip to content

Default MasterKey enforcement to Strict so an empty key refuses startup

Placeholder ppxd requested to merge fix/p5-master-key-strict-default into main

Summary

  • The MasterKey enforcement default was Warn: an empty / too-short / all-zero key logged a warning and proceeded with a recoverable 0-byte-derived key, so at-rest encryption was theater by default on a misconfigured deploy. Flip the default to Strict — a missing or weak key now refuses startup instead of silently encrypting under a recoverable key.
  • Non-breaking for real deployments: they always configure a real master key, so the Strict default never fires for them; it only catches a genuine misconfiguration (key never set). Operators who deliberately run without at-rest protection opt out with SQUID_MASTER_KEY_ENFORCEMENT=warn (allow + log) or =off (silent).
  • Scoped to MasterKey only: ReadEnforcementMode() passes Strict explicitly rather than changing the shared EnforcementModeReader.Read default, so the other hardening consumers (cert validation, sensitive-variable decrypt) keep their Warn default.
  • The committed appsettings.json MasterKey stays intentionally empty so a real deploy must supply its own. The E2E test config's previously all-zero key is replaced with a real random key so the E2E container still boots under Strict.

This completes the deferred dependency that made the P2 at-rest encryption cluster (#437/#438/#439) genuinely protective rather than cosmetic-by-default.

Behaviour change & opt-out

MasterKey Before (Warn default) After (Strict default)
real 32-byte random boots boots
empty / missing boots + warns (0-byte key) refuses startup
too short / all-zero boots + warns refuses startup

Opt-out preserved: SQUID_MASTER_KEY_ENFORCEMENT=warn or =off restores the prior lenient behaviour. The other hardening env vars are unaffected.

Test plan

  • Unit — invert the constructor backward-compat test (empty key now throws under the Strict default) + add Constructor_ValidMasterKey_DoesNotThrow (happy path boots) and Constructor_EmptyMasterKey_WarnOptOut_DoesNotThrow (opt-out). Hermetic: env var saved/cleared/restored, class moved into the serialised collection so the process-wide mutation can't race.
  • Existing ValidateMasterKey(..., Strict) matrix already pins empty/short/all-zero → throw.
  • Integration (real Postgres + real DI) — new IntegrationMasterKeyStrictDefault: the container boots under the Strict default with the configured real key and round-trips a secret through the V2 envelope.
  • Full unit suite green (6037); at-rest + master-key integration green (13/13).

Merge request reports

Loading