namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions; /// /// ⚠️ DEPRECATED compatibility path (lmxopcua#815) — the ONE switch between "enforce" and "warn-only" credential /// resolution, and it is chosen by CALL SITE, never by a global flag: a deployed, running driver (Galaxy /// InitializeAsync/ReopenAsync + ack fallback, OpcUaClient session open) passes its instance's tracker /// and so keeps resolving a pre-#815 out-of-namespace reference with its OLD semantics, warning instead of /// faulting; every admin-node path (Test Connect probe, AdminUI live browse) and Heidenhain pass /// and stay hard-fail. /// /// /// /// Why it is safe to keep for running drivers: the AdminUI save path and the deploy gate refuse every /// out-of-namespace reference, so a Designer can no longer CREATE one — what remains are configs deployed /// before the upgrade, whose endpoint and reference an administrator already deployed. Such a config runs /// only until its next deploy (which the gate refuses until it is migrated). /// /// /// Removing it (the planned hard-fail release): delete this type, the per-driver Legacy… resolve /// methods it gates and the legacy parameters; every caller then takes the enforce branch it already /// has. Nothing else changes. /// /// /// Each use is reported through at most once per per field /// (a Galaxy gateway reconnect re-resolves every time), and is exposed as so the /// driver's bag carries it to the AdminUI (/hosts and the /raw /// credential banner). Messages name the driver, field and allowed forms — never the value. /// /// /// The driver's scope (for the allowed forms). /// The driver instance (for messages). /// Sink for a rate-limited warning line. /// Clock (tests); null ⇒ . public sealed class LegacyCredentialReferenceTracker( CredentialScope scope, string driverInstanceId, Action warn, Func? clock = null) { /// The key prefix (one entry per field). public const string DiagnosticsKeyPrefix = "credential-compat:"; /// Minimum spacing between two warnings for the same field. public static readonly TimeSpan WarnInterval = TimeSpan.FromHours(1); private readonly object _gate = new(); private readonly Dictionary _lastWarned = new(StringComparer.Ordinal); private readonly SortedDictionary _diagnostics = new(StringComparer.Ordinal); /// The scope this tracker reports against. public CredentialScope Scope => scope; /// The current legacy uses, keyed credential-compat:<field>. public IReadOnlyDictionary Diagnostics { get { lock (_gate) return new Dictionary(_diagnostics, StringComparer.Ordinal); } } /// Records that was resolved through the legacy path (warns, rate-limited). /// The field label. public void Report(string field) { var forms = $"secret:{scope.SecretNamespaces[0]} or env:{scope.EnvVariablePrefixes[0]}"; Record( field, $"out-of-namespace ref still resolved (#815 warn-only, deprecated); next deploy refuses it — use {forms}", $"{scope.Label} driver '{driverInstanceId}': {field} is a credential reference outside the {scope.Label} " + $"namespace. It is still resolved for this already-deployed driver (lmxopcua#815 warn-only " + $"compatibility — deprecated, a later release fails closed) but the deploy gate refuses it: migrate " + $"it to {forms} before the next deploy. See docs/security.md §\"Credential references are namespaced per driver\"."); } /// /// Records that the key-material file field (lmxopcua#836, e.g. OpcUaClient /// UserCertificatePath) was loaded from a path outside the node's certificate directory through the same /// warn-only window (warns, rate-limited). Never carries the path. /// /// The field label. /// How the operator configures the directory. public void ReportPath(string field, string directoryDescription) => Record( field, $"path outside the certificate directory still loaded (#836 warn-only, deprecated); next deploy refuses it — " + $"move the file under {directoryDescription} and author it as a relative path", $"{scope.Label} driver '{driverInstanceId}': {field} is not a relative path inside the node's certificate " + $"directory ({directoryDescription}). It is still loaded for this already-deployed driver (lmxopcua#836 " + "warn-only compatibility — deprecated, a later release fails closed) but the deploy gate refuses it: move " + "the file into that directory and set the field to its relative path before the next deploy. See " + "docs/security.md §\"Key-material file paths are confined to a node directory\"."); private void Record(string field, string diagnostic, string warning) { var now = (clock ?? (() => DateTime.UtcNow))(); bool log; lock (_gate) { _diagnostics[DiagnosticsKeyPrefix + field] = diagnostic; log = !_lastWarned.TryGetValue(field, out var last) || now - last >= WarnInterval; if (log) _lastWarned[field] = now; } if (log) warn(warning); } }