docs(secrets): G-5 master-key operator note (Environment provider via NSSM env)
This commit is contained in:
@@ -255,6 +255,88 @@ When LDAP is enabled, `Server`, `SearchBase`, `ServiceAccountDn`,
|
||||
must be in range. See `glauth.md` for the shared dev instance and the
|
||||
dev→production hardening posture.
|
||||
|
||||
## Secrets Master Key
|
||||
|
||||
`${secret:...}` tokens in configuration — currently just
|
||||
`MxGateway:Ldap:ServiceAccountPassword` (see above) — are resolved at startup
|
||||
from an encrypted local SQLite store, bound from the top-level `Secrets`
|
||||
section (a sibling of `MxGateway`, not nested under it):
|
||||
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `Secrets:SqlitePath` | `mxgateway-secrets.db` | Path to the encrypted secrets store, resolved relative to the app content root when not rooted. |
|
||||
| `Secrets:MasterKey:Source` | `Environment` | Key-encryption-key (KEK) provider. `Environment` reads a base64-encoded 32-byte key from an env var; `Dpapi` uses a machine-bound key file instead (see below). |
|
||||
| `Secrets:MasterKey:EnvVarName` | `ZB_SECRETS_MASTER_KEY` | Env var name the `Environment` provider reads the KEK from. |
|
||||
|
||||
The gateway always opens (and, if needed, creates and schema-migrates) this
|
||||
store at startup, even when zero `${secret:}` tokens are configured.
|
||||
|
||||
### Delivering the master key on the NSSM box
|
||||
|
||||
MxAccessGateway is a single Windows/NSSM box with no clustering, so the
|
||||
simplest posture applies: the `Environment` provider delivers
|
||||
`ZB_SECRETS_MASTER_KEY` via the NSSM service environment — the same mechanism
|
||||
already used for `MxGateway__Ldap__ServiceAccountPassword` overrides and the
|
||||
other `AppEnvironmentExtra` entries (see `wonder-app-vd03`/`windev` deploy
|
||||
notes). The key is **never committed** to `appsettings.json` or the repo.
|
||||
|
||||
```text
|
||||
nssm get MxAccessGw AppEnvironmentExtra
|
||||
nssm set MxAccessGw AppEnvironmentExtra ZB_SECRETS_MASTER_KEY=<base64-32-bytes>
|
||||
```
|
||||
|
||||
`AppEnvironmentExtra` is the same newline-separated `KEY=VALUE` block that
|
||||
already carries `ASPNETCORE_ENVIRONMENT`, `MxGateway__Worker__ExecutablePath`,
|
||||
and the API-key pepper (see the `wonder-app-vd03`/`windev` deploy notes) —
|
||||
`nssm set ... AppEnvironmentExtra` **replaces the entire block**, so always
|
||||
`get` first and re-include every existing entry alongside the new key, then
|
||||
restart the service for it to take effect.
|
||||
|
||||
### Seeding a secret
|
||||
|
||||
Once the master key is in place, seed (or update) a secret on the box with
|
||||
the `secret` CLI (`ZB.MOM.WW.Secrets.Cli`). The CLI is config-driven — it
|
||||
reads `Secrets:SqlitePath` and the master-key settings from its own
|
||||
`appsettings.json` plus environment variables, exactly like the gateway — so
|
||||
run it with the same `ZB_SECRETS_MASTER_KEY` set and, if it isn't colocated
|
||||
with a config that already points at the gateway's store, an explicit
|
||||
`Secrets__SqlitePath` override so both processes read/write the same file:
|
||||
|
||||
```text
|
||||
set ZB_SECRETS_MASTER_KEY=<base64-32-bytes>
|
||||
set Secrets__SqlitePath=C:\ProgramData\MxGateway\mxgateway-secrets.db
|
||||
secret set ldap/mxgateway/bind <the real LDAP bind password>
|
||||
```
|
||||
|
||||
A mismatched `Secrets:SqlitePath` between the CLI and the gateway silently
|
||||
seeds a different store, and the gateway then fails closed at startup as if
|
||||
the secret were never set.
|
||||
|
||||
### Fail-closed
|
||||
|
||||
If a referenced `${secret:...}` token cannot be resolved — the secret is
|
||||
missing from the store, or `ZB_SECRETS_MASTER_KEY` is absent or wrong — the
|
||||
gateway refuses to start (`SecretNotFoundException`, or a master-key error
|
||||
raised before that). There is no fallback to a blank or leaked credential.
|
||||
|
||||
### Alternative: DPAPI
|
||||
|
||||
`Secrets:MasterKey:Source = Dpapi` uses a machine-bound key file instead of an
|
||||
env var — stronger at-rest binding (the key never appears in the service
|
||||
environment or process listing) at the cost of tying the store to one
|
||||
machine, which complicates DR restore (the key file does not travel with a
|
||||
backup/restore of the `.db` file alone). Not used on the current NSSM
|
||||
deployments; documented here as the alternative posture if the env-var
|
||||
exposure becomes a concern.
|
||||
|
||||
### Rotation / DR
|
||||
|
||||
Losing the KEK makes the store permanently unrecoverable — there is no
|
||||
recovery path other than re-seeding every secret from scratch. Back the KEK
|
||||
up in the org secret vault, not just on the box. Full KEK rotation
|
||||
(re-wrapping every secret under a new key, `RewrapAll`) is a future item and
|
||||
is **not yet available**.
|
||||
|
||||
## Protocol Options
|
||||
|
||||
| Option | Default | Description |
|
||||
|
||||
Reference in New Issue
Block a user