using ZB.MOM.WW.Configuration; using ZB.MOM.WW.OtOpcUa.Core.Authorization; namespace ZB.MOM.WW.OtOpcUa.Host.Configuration; /// /// Fail-fast startup validator for (R03:2.3). The pepper used to be /// checked only when the first key was verified — ApiKeySecret.Verify throws on an empty one — /// so a site node deployed without Security__ApiKeys__Pepper booted green, passed health, /// applied deployments and denied every machine client with the same opaque "Invalid API key" until /// someone read the log; a central node with a wrong pepper minted keys that could verify nowhere. /// Every other security section already fails host start on a bad value; this one now does too. /// /// /// Registered for hasDriver || hasAdmin: a driver node needs the pepper to VERIFY keys, an admin /// node to MINT them. The minimum length is a floor against a placeholder ("x") passing as /// configured, not a strength claim — the pepper is a server-side secret, not a password. /// public sealed class ApiKeyOptionsValidator : OptionsValidatorBase { /// The shortest pepper accepted (characters, after trimming). public const int MinPepperLength = 16; /// protected override void Validate(ValidationBuilder builder, ApiKeyOptions options) { var pepper = options.Pepper?.Trim() ?? string.Empty; builder.RequireThat(pepper.Length > 0, $"{ApiKeyOptions.SectionName}:Pepper is required on any node with the driver or admin role — " + "supply it via Security__ApiKeys__Pepper or a ${secret:} reference. An empty pepper would reject " + "every API key (driver) or mint keys that can never verify (admin)."); builder.RequireThat(pepper.Length == 0 || pepper.Length >= MinPepperLength, $"{ApiKeyOptions.SectionName}:Pepper must be at least {MinPepperLength} characters."); builder.RequireThat(!string.IsNullOrWhiteSpace(options.Username), $"{ApiKeyOptions.SectionName}:Username must not be blank."); builder.RequireThat(options.StoreTimeoutMs >= 0, $"{ApiKeyOptions.SectionName}:StoreTimeoutMs must be >= 0 (0 disables the deadline)."); builder.RequireThat(options.StoreCacheSeconds >= 0, $"{ApiKeyOptions.SectionName}:StoreCacheSeconds must be >= 0 (0 disables the cache)."); } }