Files
scadaproj/components/secrets/current-state/otopcua/CURRENT-STATE.md
T
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

4.3 KiB

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.