docs: #873 SDK 2.0.1 API-key logins need client clock within 5 min ahead of the server (rollout runbook + security guide)
v2-ci / unit-tests (push) Waiting to run

Claude-Session: https://claude.ai/code/session_01GT67AD99zQQqpLCVm3p7xk
This commit is contained in:
Joseph Doherty
2026-11-11 18:08:39 -05:00
parent 43f8b8e85a
commit 5ac5520532
2 changed files with 16 additions and 0 deletions
@@ -30,6 +30,17 @@ and smoke record; this runbook supplies no invented Linux service-unit name.
Do not use `scripts/install/Refresh-Services.ps1` as a blanket upgrade action: it
also touches gateway/retired sidecar paths outside this SDK rollout.
**Clock sync is a prerequisite (#873).** Under SDK 2.0.1 a client whose UserName password is longer
than 64 bytes encrypts it as `RsaEncryptedSecret`, which carries a signing time. OtOpcUa API keys are
65 characters, so every SDK 2 client, including our CLI and the OpcUaClient driver, does this. The
2.0.1 server rejects the token with `BadIdentityTokenInvalid` if the client's signing time is more
than **5 minutes ahead** of the server's clock, or more than 1 hour behind it. On windev, a clock
549 s slow made every API-key login from the Mac fail, while the same key from the same host
succeeded. Before upgrading, confirm the Windows Time service is running and the offset is small on
the server **and** every SDK 2 client host (`w32tm /query /status`). SDK 1.5 clients (ScadaBridge
today, ScadaBridge#125) are not affected. Since #874 the server logs these refusals under the SDK's
`Opc.Ua.*` categories.
## Preserved contract
- Existing raw/UNS namespace URIs, identifier forms, endpoint profiles, API-key/anonymous
+5
View File
@@ -210,6 +210,11 @@ The Server accepts three OPC UA identity-token types:
| UserName/Password | `ApiKeyOpcUaUserAuthenticator.AuthenticateUserNameAsync` (`src/Server/ZB.MOM.WW.OtOpcUa.Host/OpcUa/ApiKeyOpcUaUserAuthenticator.cs`, implements `IOpcUaUserAuthenticator`), backed by an `IApiKeyStore`. | The username is the literal `apikey` and the password is an `otua_<keyId>_<secret>` credential (#520). One indexed lookup plus one peppered HMAC-SHA256, no network I/O. The key resolves to a **principal**, whose `NodeAcl` grants become roles on a `RoleCarryingUserIdentity`. **LDAP is not on this path** — it backs AdminUI login only. |
| X.509 Certificate | Stack-level acceptance during the secure-channel handshake. | The certificate must be trusted (see PKI trust flow). It carries **no** roles, so a cert-only session is read-everything / write-nothing. |
> **SDK 2.0.1 clock window (#873).** An SDK 2 client sends the API key as an `RsaEncryptedSecret` with a
> signing time. The server rejects it with `BadIdentityTokenInvalid` when the client clock is more than
> 5 minutes ahead of the server's, or more than 1 hour behind it. Keep client and server clocks in sync. The
> refusal is logged under the SDK's `Opc.Ua.*` categories (#874); SDK 1.5 clients are unaffected.
When no authenticator is supplied, `OpcUaApplicationHost` falls back to `NullOpcUaUserAuthenticator`;
the Host wires the real `ApiKeyOpcUaUserAuthenticator` as a singleton in `Program.cs`. Which
**`IApiKeyStore`** backs it depends on the node's role: