feat(acl): keys + ACLs reach driver-only nodes via the artifact (#520 stage 4)
A driver-only node has had no ConfigDb since per-cluster mesh Phase 4, so it registered NullOpcUaUserAuthenticator and denied every UserName token. Its keys now ride the deployment artifact instead. - ConfigComposer snapshots ApiKeys alongside the NodeAcls it already carried. Only the peppered HMAC ships, never a secret, and the pepper is node config that is deliberately not in the artifact. - DeploymentArtifact.ParseDataPlaneAuthority decodes both node-side. Grants are scoped to the node's own cluster; keys are not, because ApiKey carries no ClusterId — a key is a fleet-wide credential and the ACL rows are what bind it. - OpcUaPublishActor installs them on DataPlaneAuthorityState on every apply, BEFORE its plan.IsEmpty early-return: an artifact that only revokes a key produces an empty address-space plan, so feeding after that return would leave the revoked key working until an unrelated structural change redeployed. - ArtifactApiKeyStore serves them to session activation; a node with a ConfigDb keeps ConfigDbApiKeyStore, whose per-request read is what makes revocation immediate there. - OtOpcUaNodeManager gains DataPlaneAuthority — the cluster identity blocker #4 named. NodeScope.ClusterId is required and the evaluator fails closed on a mismatch, so until now no scope it built could have evaluated. Nothing reads it until stages 5/6. The generation is a node-local COUNTER driven by the RevisionHash, not derived from its bits. PermissionTrieCache treats the highest GenerationId as current, so a generation taken from hash bytes would install a lower number about half the time and the cache would go on serving the previous deployment's ACLs, silently. Sessions are not stamped with it yet — that belongs with the first seam that reads a UserAuthorizationState, in stage 5, rather than being built now and consulted by nothing. Live-gated on docker-dev against site-a-1 (driver-only, no ConfigDb): key authenticates, wrong secret denied, both halves of the revocation asymmetry observed, and a SITE-A grant confirmed not to reach site-b-1. Key minted with an independent Python HMAC so the verifier was checked against something other than itself. Rig left clean. Claude-Session: https://claude.ai/code/session_015p7wGqy3YpZNCpDzTpGMKo
This commit is contained in:
@@ -10,12 +10,12 @@ namespace ZB.MOM.WW.OtOpcUa.Host.OpcUa;
|
||||
/// that has a ConfigDb — i.e. one carrying the <c>admin</c> role, including a fused admin+driver node.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// ⚠️ <b>A driver-only node has no ConfigDb since per-cluster mesh Phase 4</b>, so this store cannot
|
||||
/// be registered there and such a node has no key source until the artifact-backed store lands in
|
||||
/// stage 4. Until then a driver-only node authenticates no UserName tokens at all — it falls to
|
||||
/// <see cref="OpcUaServer.Security.NullOpcUaUserAuthenticator"/>, which denies every attempt. That is
|
||||
/// the safe direction, and anonymous sessions (still permitted, and still unrestricted until
|
||||
/// enforcement lands in stages 5–6) are unaffected.
|
||||
/// <b>A driver-only node has no ConfigDb since per-cluster mesh Phase 4</b>, so this store cannot be
|
||||
/// registered there; that node uses <see cref="ArtifactApiKeyStore"/> instead (#520 stage 4).
|
||||
/// <para>
|
||||
/// Reading per request is what makes revocation immediate here, which is why this store is
|
||||
/// preferred wherever a ConfigDb exists — including on a fused admin+driver node.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class ConfigDbApiKeyStore : IApiKeyStore
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user