review(Host): allow-anonymous /metrics + unconditional LDAP validator

Code review at HEAD 7286d320. Host-001 (High): /metrics was auth-gated on admin
nodes (Prometheus 401) -> AllowAnonymous. Host-002: register LdapOptionsValidator
unconditionally for fail-fast startup validation on admin-only nodes. Host-004: fix
metrics XML doc. Host-003 (docs) left Open.
This commit is contained in:
Joseph Doherty
2026-06-19 10:22:59 -04:00
parent d23e585cdb
commit e4abe186a1
3 changed files with 119 additions and 5 deletions
@@ -37,14 +37,16 @@ public static class ObservabilityExtensions
}
/// <summary>
/// Mounts the Prometheus scrape endpoint on the existing ASP.NET pipeline. Call after
/// <c>app.UseAuthentication/UseAuthorization</c> if metrics access should require auth;
/// the default leaves it unauthenticated for local Prometheus scrapes.
/// Mounts the Prometheus <c>/metrics</c> scrape endpoint on the existing ASP.NET pipeline.
/// The endpoint is explicitly marked <c>AllowAnonymous</c> so unauthenticated Prometheus
/// scrapers can reach it regardless of the host's auth fallback policy (which on admin-role
/// nodes is <c>RequireAuthenticatedUser</c>). This mirrors the behaviour of
/// <c>MapZbHealth</c>, which also marks its endpoints anonymous.
/// </summary>
/// <param name="app">The endpoint route builder.</param>
public static IEndpointRouteBuilder MapOtOpcUaMetrics(this IEndpointRouteBuilder app)
{
app.MapZbMetrics();
app.MapZbMetrics().AllowAnonymous();
return app;
}
}
+9 -1
View File
@@ -82,6 +82,13 @@ builder.AddZbSerilog(o => o.ServiceName = "otopcua");
builder.Services.AddOtOpcUaConfigDb(builder.Configuration);
builder.Services.AddOtOpcUaCluster(builder.Configuration);
// Validate LdapOptions unconditionally so ANY role node (admin-only, driver-only, or fused)
// fails fast at startup on a misconfigured or insecure-transport LDAP section.
// The validator's own guard (if !Enabled || DevStubMode) keeps it a safe no-op on disabled/dev
// configs. Previously this was inside the hasDriver block, which left admin-only nodes without
// the startup check (AddOtOpcUaAuth binds LdapOptions but does not attach a validator).
builder.Services.AddValidatedOptions<LdapOptions, LdapOptionsValidator>(builder.Configuration, LdapOptions.SectionName);
if (hasDriver)
{
builder.Services.AddOtOpcUaRuntime();
@@ -160,7 +167,6 @@ if (hasDriver)
ScriptRootLoggerFactory.Build(
sp.GetRequiredService<IScriptLogPublisher>(), scriptLogFilePath, scriptLogTopicMinLevel, Serilog.Log.Logger)));
builder.Services.AddValidatedOptions<LdapOptions, LdapOptionsValidator>(builder.Configuration, LdapOptions.SectionName);
// TryAdd so a fused admin+driver node (where AddOtOpcUaAuth also registers these) ends up
// with exactly one descriptor; on a driver-only node these are the sole registrations.
// OtOpcUaLdapAuthService is the app ILdapAuthService (Enabled switch + DevStubMode over the
@@ -168,6 +174,8 @@ if (hasDriver)
// <string> per call to turn the directory's groups into roles, so register it here for driver-
// only nodes (AddOtOpcUaAuth registers it on admin nodes); ILdapGroupRoleMappingService it
// depends on is already registered unconditionally by AddOtOpcUaConfigDb above.
// Note: AddValidatedOptions<LdapOptions, LdapOptionsValidator> is now registered unconditionally
// above both role blocks so admin-only nodes also get fail-fast LDAP startup validation.
builder.Services.TryAddSingleton<ILdapAuthService, OtOpcUaLdapAuthService>();
builder.Services.TryAddScoped<IGroupRoleMapper<string>, OtOpcUaGroupRoleMapper>();
builder.Services.AddSingleton<IOpcUaUserAuthenticator, LdapOpcUaUserAuthenticator>();