e347286f28
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.
62 lines
5.1 KiB
Markdown
62 lines
5.1 KiB
Markdown
# 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.
|