docs(secrets): package README + embed in nupkgs
This commit is contained in:
@@ -7,6 +7,12 @@
|
|||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<Version>0.1.0</Version>
|
<Version>0.1.0</Version>
|
||||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||||
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
</PropertyGroup>
|
</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>
|
</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