Files
Joseph Doherty e347286f28 docs(components): normalize Secrets component + index the shared lib
Add components/secrets/ (SPEC, realized shared-contract, code-verified
current-state for OtOpcUa/mxaccessgw/ScadaBridge, GAPS adoption backlog) and
register the Secrets row in CLAUDE.md + components/README.md. The
ZB.MOM.WW.Secrets lib is built + published 0.1.2 + reference-consumer-proven
(HistorianGateway, live vs the wonder historian); per-app adoption is the
tracked follow-on.
2026-07-16 04:49:57 -04:00

5.1 KiB

ScadaBridge — secrets current state

Descriptive baseline (code-verified) of how ScadaBridge handles secrets today, before adopting ZB.MOM.WW.Secrets. Paths are relative to ~/Desktop/ScadaBridge.

Where each secret lives today

Secret Location today At-rest protection
MSSQL ConfigDb password ScadaBridge:Database:ConfigurationDb connstr (Host/Program.cs:221-223); committed dev plaintext docker/central-node-a/appsettings.Central.json:21 Plaintext
Central dbo MachineDataDb password ScadaBridge:Database:MachineDataDb (StartupValidator.cs:63-65; appsettings.Central.json:22) Plaintext
Site SQLite SiteDbPath / StoreAndForward SqliteDbPath (appsettings.Site.json:26,34) — plain file paths None (no SQLCipher)
LDAP service-account password ScadaBridge:Security:Ldap:ServiceAccountPassword via ZB.MOM.WW.Auth.Ldap (Program.cs:140-142); committed serviceaccount123 appsettings.Central.json:32 (+ loose ldap_login.txt etc. at repo root) Plaintext
Inbound /api keys Hashed (peppered-HMAC) in SQLite data/inbound-api-keys.sqlite via ZB.MOM.WW.Auth.ApiKeys (Program.cs:184-217); legacy SQL-Server key store retired (20260602092753_RetireInboundApiKeyStore) Hashed (not reversible)
API-key pepper ScadaBridge:InboundApi:ApiKeyPepper (InboundAPI/InboundApiOptions.cs:23-36); committed dev dev-only-insecure-pepper-… docker/docker-compose.yml:18,45 (both central nodes share) Plaintext env
JWT signing key ScadaBridge:Security:JwtSigningKey (HS256, Security/SecurityOptions.cs:34); appsettings.Central.json:38 Plaintext
DatabaseConnectionDefinition.ConnectionString ConfigDb column Encrypted (Data Protection — see below)
MxGateway per-endpoint ApiKey Inside DataConnection.PrimaryConfiguration/BackupConfiguration JSON in ConfigDb; column mapped without the encrypting converter (ConfigurationDatabase/Configurations/SiteConfiguration.cs:52-56) Plaintext in ConfigDb (redacted for display/audit only, ConfigSecretScrubber.cs:18-19)
Akka remoting HOCON AkkaHostedService.cs:250-308 — hostname/port only No secure-cookie / no TLS / no shared secret (plain TCP)

Encryption-at-rest today (the one existing analog)

  • EncryptedStringConverter (ASP.NET Core Data Protection EF value converter, ConfigurationDatabase/EncryptedStringConverter.cs:20-52, purpose "…ConfigurationDatabase.EncryptedColumn") encrypts four ConfigDb columns (ScadaBridgeDbContext.cs:319-348): SmtpConfiguration.Credentials, SmsConfiguration.AuthToken, ExternalSystemDefinition.AuthConfiguration, DatabaseConnectionDefinition.ConnectionString.
  • Key ring lives in ConfigDb (IDataProtectionKeyContext + DataProtectionKeys, AddDataProtection().PersistKeysToDbContext<ScadaBridgeDbContext>()) — shared across central nodes. No SetApplicationName (discriminator = content-root path) and no ProtectKeysWith* — the key-ring keys are stored unencrypted in the DB; the DB is the only protection boundary.

Config expansion today

  • None functional. Stock AddJsonFile+AddEnvironmentVariables+AddCommandLine (Program.cs:38-43). The ${SCADABRIDGE_*} tokens in appsettings.Central.json:23,33,35 are literal, non-functional placeholders — the real values arrive by whole-key env override (ScadaBridge__Security__Ldap__ServiceAccountPassword, etc.), documented in the _secrets note (appsettings.Central.json:21). StartupValidator (ConfigPreflight from the shared ZB.MOM.WW.Configuration package) validates presence/shape only — no resolution.

Adoption plan (toward SPEC)

  1. Formalize the existing placeholders: the non-functional ${SCADABRIDGE_*} + whole-key-override convention is exactly the pattern ${secret:} replaces — swap the plaintext ConfigDb/MachineDataDb passwords, LDAP password, JWT signing key, and pepper to ${secret:…} tokens resolved pre-host, before StartupValidator/ConfigPreflight runs. Delete the committed dev plaintext + the loose *_login.txt files.
  2. Close the MxGateway ApiKey plaintext-in-ConfigDb gap — either extend EncryptedStringConverter to that JSON column or resolve it via ISecretResolver.
  3. Clustered sync: ScadaBridge is Akka-clustered (central pair + site nodes). Two SPEC- sanctioned options: (a) point ISecretStore at the shared ConfigDb (SQL-Server store) — one source of truth, mirrors how the Data-Protection key ring is already shared; or (b) adopt the ZB.MOM.WW.Secrets.Akka replicator (built on adoption) with a shared KEK (FileMasterKeyProvider, same mounted key on every node — hard requirement).
  4. UI: mount /admin/secrets in CentralUI; map ScadaBridge's admin role onto the policies.
  5. Keep bespoke: the existing Data-Protection column encryption can coexist (or migrate); the peppered-HMAC inbound-key SQLite store stays (already hashed). Akka remoting auth/TLS is a separate hardening concern, not this component.