Skip to content

Encrypt Certificate.Password + CertificateData at rest

Placeholder ppxd requested to merge feat/encrypt-certificate-secrets-at-rest into main

Summary

  • Encrypt a certificate's private-key material at rest — both Certificate.Password (the PFX password) AND Certificate.CertificateData (the PFX/PEM blob), previously cleartext (so the PFX password protection was moot) — in CertificateDataProvider, the single seam every certificate read/write funnels through (deploy-pipeline client-cert auth via EndpointContextBuilder, the cert-variable expander, the certificate service). Reuses the IVariableEncryptionService V2 envelope. Same proven pattern as DeploymentAccount.Credentials (#437) and ExternalFeed.Password (#438). Makes the long-stale Certificate.cs "encrypted at rest" comment finally true.
  • Non-breaking / zero migration: read-both via the SQUID_ENCRYPTED_V2: prefix (unprefixed plaintext returned verbatim → pre-existing rows still load, upgrade lazily). Idempotent encrypt-on-write. No service-layer change — CertificateDto exposes only PasswordHasValue/HasPrivateKey.
  • Reads detach-then-decrypt (new IRepository.Detach) rather than QueryNoTracking. Detaching gives the same flush-safety as #437/#438 (the in-place decrypt is never flushed back as plaintext by a later shared-scope SaveChanges) and frees the identity map, so the existing single-scope create-then-update/delete CRUD tests don't hit a duplicate-tracking conflict. (QueryNoTracking is production-safe for account/feed since each mediator request is a fresh scope; the certificate CRUD tests run multiple ops in one scope, which detach handles cleanly.)
  • Reuses the existing Security:VariableEncryption:MasterKey — no new key, no hardcoded default (P5 key-lifecycle hardening tracked separately).

Test plan

  • Integration (real Postgres, IntegrationCertificateSecretsAtRest, 4): both columns carry SQUID_ENCRYPTED_V2: at rest and not the cleartext secret; decrypt-on-read; read-both on a legacy plaintext row; same-scope read+decrypt+SaveChanges keeps both columns encrypted (flush regression); a metadata-only update preserves + re-encrypts the secrets.
  • Regression: all 34 existing IntegrationCertificateCrud integration tests + 99 Certificate unit tests stay green (the detach approach keeps the single-scope CRUD flows working). At-rest integration across Account + Feed + Certificate (12) green — the additive IRepository.Detach doesn't affect the QueryNoTracking slices.
  • Crypto-envelope contract (round-trip / read-both / tamper) is unit-covered by the shared IVariableEncryptionService tests; this persistence-layer change is covered at the integration tier (Rule 9).
  • CI: existing K8s Pipeline E2E (client-certificate auth) exercises certificate consumption via the same decrypting provider seam.

Merge request reports

Loading