docs(secrets): package README + embed in nupkgs
This commit is contained in:
@@ -7,6 +7,12 @@
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.0</Version>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Embed the repo README in each packable nupkg (ignored by non-packable projects). -->
|
||||
<ItemGroup Condition="'$(IsPackable)' == 'true'">
|
||||
<None Include="$(MSBuildThisFileDirectory)README.md" Pack="true" PackagePath="\" Visible="false" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# ZB.MOM.WW.Secrets
|
||||
|
||||
A reusable, envelope-encrypted **secrets manager** for the `ZB.MOM.WW.*` SCADA family:
|
||||
store secrets (SQL passwords, API tokens, connection strings) encrypted at rest and get the
|
||||
plaintext back on demand — from application code, from configuration, or from an operator UI.
|
||||
|
||||
## Packages
|
||||
|
||||
| Package | Purpose |
|
||||
|---|---|
|
||||
| `ZB.MOM.WW.Secrets.Abstractions` | Contracts only (`ISecretStore`, `ISecretResolver`, `IMasterKeyProvider`, `ISecretCipher`, `ISecretReplicator`, `ISecretCacheInvalidator`, `ISecretActorAccessor`), value types, exceptions. Dependency-light. |
|
||||
| `ZB.MOM.WW.Secrets` | Implementation: AES-256-GCM envelope cipher, env/file/DPAPI master-key providers, SQLite store, TTL resolver (audited), `${secret:}` config expander, `AddZbSecrets`. |
|
||||
| `ZB.MOM.WW.Secrets.Ui` | Blazor RCL on `ZB.MOM.WW.Theme`: list / add / rotate / delete + policy-gated, audited reveal. |
|
||||
|
||||
A `secret` CLI (`set` / `get` / `list` / `rm` / `rotate`) ships in the repo (not packed).
|
||||
|
||||
## How it protects secrets
|
||||
|
||||
Each value is encrypted with a fresh **data key (DEK)** using AES-256-GCM; the DEK is then
|
||||
**wrapped** by a **master KEK** that never touches the database. The AAD binds the ciphertext
|
||||
to the secret's name and the wrapped DEK to the KEK id, so rows can't be swapped and a
|
||||
tampered/mis-keyed row **fails closed** (`SecretDecryptionException`) rather than returning a
|
||||
wrong value. The master KEK is resolved through a pluggable `IMasterKeyProvider`
|
||||
(environment variable, key file, or Windows DPAPI).
|
||||
|
||||
## Wiring it up
|
||||
|
||||
```csharp
|
||||
builder.Services.AddZbSecrets(builder.Configuration, "Secrets");
|
||||
|
||||
// After config load, BEFORE options validation — expand ${secret:...} references:
|
||||
var expander = new SecretReferenceExpander(app.Services.GetRequiredService<ISecretResolver>());
|
||||
await expander.ExpandConfigurationAsync((IConfigurationRoot)builder.Configuration, ct);
|
||||
```
|
||||
|
||||
```jsonc
|
||||
// appsettings.json
|
||||
"Secrets": {
|
||||
"SqlitePath": "secrets.db",
|
||||
"MasterKey": { "Source": "Environment", "EnvVarName": "ZB_SECRETS_MASTER_KEY" },
|
||||
"RunMigrationsOnStartup": true,
|
||||
"ResolveCacheTtl": "00:00:30"
|
||||
},
|
||||
// Keep plaintext out of config — reference a stored secret instead:
|
||||
"ConnectionStrings": {
|
||||
"Historian": "Server=...;User Id=sa;Password=${secret:sql/historian-password}"
|
||||
}
|
||||
```
|
||||
|
||||
The master key is 32 bytes, provided base64 in `ZB_SECRETS_MASTER_KEY` (Environment source).
|
||||
A missing/invalid key fails closed at startup (`MasterKeyUnavailableException`).
|
||||
|
||||
## Runtime + human access
|
||||
|
||||
- **App code:** inject `ISecretResolver` and call `GetAsync(name, ct)`. Every resolve is
|
||||
audited via `ZB.MOM.WW.Audit` (name + outcome only — the value is never logged).
|
||||
- **Operators:** mount the `ZB.MOM.WW.Secrets.Ui` `/admin/secrets` page. The list shows
|
||||
**metadata only**; revealing a value requires the `secrets:reveal` authorization policy and
|
||||
is audited. Add/rotate/delete require `secrets:manage`.
|
||||
|
||||
## Clustered deployments
|
||||
|
||||
Secrets storage is local SQLite by default. The schema already carries the
|
||||
`revision` / `updated_utc` / tombstone columns and an `ISecretReplicator` seam for
|
||||
cluster replication, but the Akka replicator (`ZB.MOM.WW.Secrets.Akka`) is a deferred
|
||||
follow-on. **When replicating across a node pair, every node must resolve the _same_ master
|
||||
KEK** (e.g. the same mounted key file) — a row wrapped by an unknown KEK fails closed.
|
||||
Reference in New Issue
Block a user