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

55 lines
4.3 KiB
Markdown

# OtOpcUa — secrets current state
Descriptive baseline (code-verified) of how OtOpcUa handles secrets **today**, before
adopting `ZB.MOM.WW.Secrets`. Paths are relative to `~/Desktop/OtOpcUa`.
## Where each secret lives today
| Secret | Location today | At-rest protection |
|---|---|---|
| ConfigDb (SQL Server) connstr password | Connection string `ConfigDb` / env `OTOPCUA_CONFIG_CONNECTION` (`…Configuration/ServiceCollectionExtensions.cs:10,21-25`; `DesignTimeDbContextFactory.cs:16,25-31` — "Do not add a plaintext password as a fallback"); not in committed appsettings | Whatever config source supplies it (plaintext) |
| LDAP service-account password | `Security:Ldap:ServiceAccountPassword` (`Security/Ldap/LdapOptions.cs:22,60,63`, bound `Security/ServiceCollectionExtensions.cs:41`); not in committed appsettings | None |
| Deploy API key | `Security:DeployApiKey` compared via `FixedTimeEquals` (`AdminUI/Api/DeployApiEndpoints.cs:30-54`); disabled when unset | Plaintext config value |
| HistorianGateway client API key | Env `ServerHistorian__ApiKey` (`Runtime/Historian/ServerHistorianOptions.cs:41-46`; appsettings ships `""` + "NEVER commit") | None (env at rest) |
| Galaxy/MxAccess gateway API key | `GalaxyDriverOptions.ApiKeySecretRef` resolved by `GalaxySecretRef.cs:43-88` into 4 forms: `env:` / `file:` / `dev:` / literal; stored in the driver `DriverConfig` JSON in the **central config DB** | `env:`/`file:` indirect out; `dev:`/literal are **cleartext in the DB** (self-documented `GalaxySecretRef.cs:86`) |
| OPC UA client-driver session + PFX passwords | `OpcUaClientDriverOptions.Password` / `UserCertificatePassword` in the driver `DriverConfig` JSON in the config DB (`Driver.OpcUaClient/OpcUaClientDriver.cs:382,495`) | **Plaintext** in the config DB |
| JWT signing key | `Security:Jwt:SigningKey` (HS256, `Jwt/JwtOptions.cs:5-9`); not in committed appsettings | Plaintext config value |
End-user login/OPC-UA passwords flow in-memory only (never persisted); the desktop client
deliberately re-prompts each launch.
## Encryption-at-rest today
- The only mechanism is **ASP.NET Core Data Protection** for the auth cookie/JWT payload —
**not** for any credential above: `Security/ServiceCollectionExtensions.cs:73-75`
(`AddDataProtection().PersistKeysToDbContext<OtOpcUaConfigDbContext>().SetApplicationName("OtOpcUa")`),
key ring in the ConfigDb `DataProtectionKeys` table (`OtOpcUaConfigDbContext.cs:70-73`).
No DPAPI, no `ProtectKeysWith*` — the key ring itself is unencrypted-at-rest in SQL.
- **Log redaction** (not at-rest): `Configuration/LocalCache/ResilientConfigReader.cs:76-89`
scrubs `Password|Pwd|User Id|AccessToken|Api-Key` fragments before logging.
## Config expansion today
- **None** as a general mechanism (no `${...}` expander; stock
`AddJsonFile`+`AddEnvironmentVariables`+`AddCommandLine`, `Host/Program.cs:59-73`). The one
partial exception is the driver-level **`GalaxySecretRef`** (`env:`/`file:`/`dev:`/literal
indirection, `GalaxySecretRef.cs:23`) which already anticipates "a future PR can swap any
of these arms for a DPAPI-backed lookup" — a natural hook for `${secret:}`.
## Adoption plan (toward SPEC)
1. **Highest value first:** the driver `DriverConfig` secrets that sit **cleartext in the
config DB** — the OpcUaClient `Password`/`UserCertificatePassword` and the `dev:`/literal
Galaxy API-key arm. Add a `secret:` arm to `GalaxySecretRef` (and the OpcUaClient options)
resolving through `ISecretResolver`, replacing the cleartext-in-DB path.
2. **Pre-host `${secret:}`** for `Security:Jwt:SigningKey`, `Security:Ldap:ServiceAccountPassword`,
`Security:DeployApiKey`, and the `ConfigDb` connstr password — expander runs before the
existing `Ldap`/`OpcUa` options validators.
3. **Master key:** env provider (`ZB_SECRETS_MASTER_KEY`) per node. **Clustered:** OtOpcUa is
Akka-clustered, so every node must resolve the **same KEK** — use `FileMasterKeyProvider`
with a shared mounted key, and when the `ZB.MOM.WW.Secrets.Akka` replicator is built, adopt
it (or point `ISecretStore` at the shared ConfigDb SQL store). See SPEC "Clustered pairs".
4. **UI:** mount `/admin/secrets` in the AdminUI; map OtOpcUa's admin role onto the two policies.
5. **Keep bespoke:** the Data-Protection cookie/JWT key ring, in-memory end-user passwords,
and the file-system OPC UA PKI Directory stores.