Files
2026-09-18 14:14:56 +02:00

76 lines
3.9 KiB
Markdown

---
status: Active
lastUpdated: 2026-09-18
applyTo:
- "Fuchs/Program.cs"
- "Fuchs/appsettings*.json"
- "OCORE/OCORE/Secrets/**"
- "OCORE_web/OCORE_web/Secrets/**"
relatedDecisions:
- "0031-shared-keyvault-dpapi-secret-management.md"
---
# Secret management (Key Vault + DPAPI)
## Summary
Fuchs loads its secrets (connection strings, API keys, mailer tokens, ...)
from the shared Azure Key Vault `pcwkeys` at startup, with a DPAPI-encrypted
local cache as an offline fallback. The mechanism is shared, host-type-agnostic
infrastructure owned by the `OCORE`/`OCORE_web` submodules ([0031](../Decisions/0031-shared-keyvault-dpapi-secret-management.md));
this doc covers how Fuchs specifically wires into it and where to make
changes. The full mechanism reference (naming/encoding rules, DI wiring,
cache format) is maintained independently in
`OCORE_web/.github_instructions/secretsmanagement.instructions.md`.
## How it works
1. `Fuchs/Program.cs` calls `builder.AddSecretManagement()`
(`OCORE_web.Secrets.SecretManagementWebExtensions`) as the first step in
`ConfigureServices`, before `FuchsOcmsIntranet.Initialize(configuration)`
or anything else reads `IConfiguration`.
2. That call, backed by `OCORE.Secrets.Extensions.SecretManagementExtensions`,
registers two configuration sources in order: the local DPAPI cache
(`secrets.cache`, offline fallback) first, then the live Azure Key Vault
(overrides the cache when reachable) using `PrefixKeyVaultSecretManager`
to filter to secrets named `fuchs--...` and map them onto
`IConfiguration` keys.
3. `KeyVaultSyncService` (a registered `BackgroundService`) periodically
re-pulls every key listed in `Fuchs:SecretManagement:ManagedSecretKeys`
from Key Vault, re-writes the DPAPI cache, and calls
`IConfigurationRoot.Reload()` so a running process picks up rotated
secrets without a restart.
4. A secret whose name is only known at runtime (not a fixed
`IConfiguration` key) — e.g. resolved from a DB job record — is instead
fetched directly via `OCORE.Secrets.Extensions.SecretsOnDemandLoader`,
bypassing `ManagedSecretKeys` and the periodic sync entirely.
5. In `Development`, `appsettings.Development.json` (git-ignored) can
override any of these values locally, bypassing Key Vault for that key;
`AssembleConnectionStrings` (`Fuchs/Program.cs`) additionally prefers
`_Dev`-suffixed credential keys so a reachable Key Vault can never hand a
dev machine production DB credentials.
## Key files
- `Fuchs/Program.cs` — `builder.AddSecretManagement()` call site and
`AssembleConnectionStrings`.
- `Fuchs/appsettings.json` — `SecretManagement` block (`VaultUri`,
`AppName: "fuchs"`, `CacheFilePath`, `SyncIntervalHours`,
`ManagedSecretKeys`) plus `"MANAGED_BY_KEYVAULT"` placeholders for every
managed key.
- `OCORE/Secrets/Extensions/SecretManagementExtensions.cs` — shared
Key Vault + DPAPI wiring and DI registration, used by every host type.
- `OCORE/Secrets/Extensions/SecretsOnDemandLoader.cs` — direct, cached,
runtime-name secret lookup outside the `ManagedSecretKeys` sync path.
- `OCORE/Secrets/Sync/KeyVaultSyncService.cs` — periodic re-sync +
`IConfigurationRoot.Reload()`.
- `OCORE/Secrets/Cache/DpapiSecretsCache.cs` — DPAPI encrypt/decrypt of the
local cache file.
- `OCORE_web/Secrets/SecretManagementWebExtensions.cs` — the
`WebApplicationBuilder.AddSecretManagement()` entry point Fuchs calls.
- `OCORE_web/.github_instructions/secretsmanagement.instructions.md` —
canonical mechanism reference (naming/encoding rules, cache format,
"adding a new secret" steps), kept current independently of this doc.
- `.github/copilot-instructions.md` ("Azure Key Vault — Secret Naming") —
the naming rules summarized for this repo, with the `fuchs--` prefix.
## Related decisions
- [0031 — Shared Key Vault + DPAPI secret management via OCORE/OCORE_web](../Decisions/0031-shared-keyvault-dpapi-secret-management.md)