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