dohertj2 dohertj2
  • Joined on 2026-02-20

ZB.MOM.WW.Secrets.Ui (0.1.3)

Published 2026-07-18 03:12:49 -04:00 by dohertj2

Installation

dotnet nuget add source --name dohertj2 --username your_username --password your_token https://gitea.dohertylan.com/api/packages/dohertj2/nuget/index.json
dotnet add package --source dohertj2 --version 0.1.3 ZB.MOM.WW.Secrets.Ui

About this package

Blazor secrets-management UI (Technical-Light) for the ZB.MOM.WW SCADA family.

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 / rewrap-all) 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

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);
// 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).

Rotating the master KEK

Because each row is a body sealed under a per-secret DEK that is wrapped by the KEK, rotating the master key only re-wraps DEKs — bodies are never re-encrypted and no value history changes. The secret rewrap-all CLI verb (backed by KekRotationService) migrates every row from the old KEK to the new one; it is idempotent and safe to re-run:

# New KEK defaults to the configured Secrets:MasterKey; supply the OLD key by env-var name or path.
ZB_SECRETS_MASTER_KEY=<new-base64>  ZB_SECRETS_OLD_KEY=<old-base64> \
  secret rewrap-all --old-key-env ZB_SECRETS_OLD_KEY
# → {"action":"rewrap-all","total":N,"rewrapped":N,"alreadyCurrent":0}

Run it with resolve traffic quiesced and once per independent store (once for a shared SQL-Server store; once per node for per-node SQLite on a shared KEK). See the operator runbook: docs/operations/kek-rotation.md.

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.

Dependencies

ID Version Target Framework
ZB.MOM.WW.Secrets.Abstractions 0.1.3 net10.0
ZB.MOM.WW.Audit 0.1.0 net10.0
ZB.MOM.WW.Auth.AspNetCore 0.1.4 net10.0
ZB.MOM.WW.Theme 0.2.0 net10.0
Novell.Directory.Ldap.NETStandard 3.6.0 net10.0
Details
NuGet
2026-07-18 03:12:49 -04:00
0
ZB.MOM.WW
23 KiB
Assets (2)
Versions (9) View all
0.3.0 2026-07-19
0.2.3 2026-07-19
0.2.2 2026-07-18
0.2.1 2026-07-18
0.2.0 2026-07-18