using Microsoft.Extensions.Logging; namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Config; /// /// Resolves Gateway.ApiKeySecretRef to the actual API-key string. Four /// forms supported, evaluated in order: /// /// env:NAME — reads Environment.GetEnvironmentVariable(NAME). /// Throws when the variable is unset, so a misconfigured deployment fails /// fast rather than silently sending an empty key. /// file:PATH — reads UTF-8 text from PATH, trimming /// whitespace. Lets operators stash the key in an ACL'd file outside the /// repo (the same pattern as the legacy .local/galaxy-host-secret.txt). /// dev:KEY — explicit cleartext literal. The dev: prefix /// is a deliberate opt-in signal (dev box, parity rig) so the resolver /// doesn't emit a warning; production should never use this arm. /// Anything else — used as the literal API key for back-compat with /// configs that pre-date this resolver. When a logger is supplied the /// resolver emits a startup warning so an operator who accidentally /// committed a cleartext key sees it. /// /// A future PR can swap any of these arms for a DPAPI-backed lookup without /// changing the call site. /// /// /// Lives in the Contracts project so both the runtime GalaxyDriver and the /// AdminUI GalaxyDriverBrowser (which intentionally don't reference each /// other) share a single resolver rather than each maintaining a copy. /// public static class GalaxySecretRef { /// /// Resolves the supplied secret reference. When the ref falls through to the /// back-compat literal arm (an unprefixed cleartext API key in /// DriverConfig JSON) and a is supplied, emits /// a . The dev: prefix is the explicit /// opt-in path that doesn't warn. /// /// The secret reference string to resolve. /// Optional logger for warning on cleartext keys. /// The resolved API-key string. public static string ResolveApiKey(string secretRef, ILogger? logger = null) { ArgumentException.ThrowIfNullOrEmpty(secretRef); if (secretRef.StartsWith("env:", StringComparison.OrdinalIgnoreCase)) { var name = secretRef[4..]; var value = Environment.GetEnvironmentVariable(name); return !string.IsNullOrEmpty(value) ? value : throw new InvalidOperationException( $"Galaxy.Gateway.ApiKeySecretRef='{secretRef}' resolves to env var '{name}', but it is unset."); } if (secretRef.StartsWith("file:", StringComparison.OrdinalIgnoreCase)) { var path = secretRef[5..]; if (!File.Exists(path)) { throw new InvalidOperationException( $"Galaxy.Gateway.ApiKeySecretRef='{secretRef}' points at '{path}', which doesn't exist."); } var contents = File.ReadAllText(path).Trim(); return !string.IsNullOrEmpty(contents) ? contents : throw new InvalidOperationException( $"Galaxy.Gateway.ApiKeySecretRef='{secretRef}' file '{path}' is empty."); } if (secretRef.StartsWith("dev:", StringComparison.OrdinalIgnoreCase)) { // Explicit dev opt-in — no warning, the operator deliberately chose a // cleartext literal (dev box, parity rig). return secretRef[4..]; } // Back-compat literal arm. An unprefixed string is treated as the literal // API key — but emit a warning so an operator who accidentally committed a // cleartext key into DriverConfig sees it. Use the dev: prefix to suppress // this warning when the literal is intentional. logger?.LogWarning( "Galaxy.Gateway.ApiKeySecretRef is being treated as a literal cleartext API key. " + "Prefer env:NAME, file:PATH, or the explicit dev:KEY prefix for dev rigs — " + "a literal key in DriverConfig JSON is stored in cleartext in the central config DB."); return secretRef; } }