From 5ac552053228de0a7b7b2378bb743a6bf811c6c3 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Wed, 11 Nov 2026 18:08:39 -0500 Subject: [PATCH] docs: #873 SDK 2.0.1 API-key logins need client clock within 5 min ahead of the server (rollout runbook + security guide) Claude-Session: https://claude.ai/code/session_01GT67AD99zQQqpLCVm3p7xk --- docs/plans/2026-11-10-opcua-sdk-2-rollout.md | 11 +++++++++++ docs/security.md | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/docs/plans/2026-11-10-opcua-sdk-2-rollout.md b/docs/plans/2026-11-10-opcua-sdk-2-rollout.md index 8a5a8fc09..0bb70b8e7 100644 --- a/docs/plans/2026-11-10-opcua-sdk-2-rollout.md +++ b/docs/plans/2026-11-10-opcua-sdk-2-rollout.md @@ -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 diff --git a/docs/security.md b/docs/security.md index a284f90c8..3502370b5 100644 --- a/docs/security.md +++ b/docs/security.md @@ -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__` 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: