# 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().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.