Files
ScadaBridge/src/ZB.MOM.WW.ScadaBridge.Host/Health/BundleSigningPolicyHealthCheck.cs
T
Joseph Doherty 559e02f5c8 feat(transport): production-boundary signing advisory, secret-key proof, runbook (#110)
Owner decisions on #110: one signing key per environment; TrustedKeys stay hand-edited;
Signing:ProductionBoundary (new, default off) logs a startup warning and reports a Degraded
bundle-signing-policy /health/ready entry while RequireSignedBundles is off; the private key
is read via ${secret:} (proved end to end, never logged); cluster pull decided not to be
built; differential deletions stay deferred. Adds docs/operations/bundle-signing-runbook.md.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
2026-11-09 22:50:18 -05:00

44 lines
1.8 KiB
C#

using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.ScadaBridge.Transport;
using ZB.MOM.WW.ScadaBridge.Transport.Signing;
namespace ZB.MOM.WW.ScadaBridge.Host.Health;
/// <summary>
/// /health/ready entry (ScadaBridge#110): Degraded while a cluster marked
/// <c>Transport:Signing:ProductionBoundary</c> still accepts unsigned bundles. Degraded maps
/// to HTTP 200 in the shared health tier, so it is visible on the Overview dashboard without
/// taking the node out of rotation. Exposes only booleans — never key material.
/// </summary>
public sealed class BundleSigningPolicyHealthCheck : IHealthCheck
{
private readonly IOptionsMonitor<TransportOptions> _options;
/// <summary>Initializes the check.</summary>
/// <param name="options">Transport options.</param>
public BundleSigningPolicyHealthCheck(IOptionsMonitor<TransportOptions> options)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
}
/// <inheritdoc />
public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context, CancellationToken cancellationToken = default)
{
var signing = _options.CurrentValue.Signing;
var data = new Dictionary<string, object>(StringComparer.Ordinal)
{
["productionBoundary"] = signing.ProductionBoundary,
["requireSignedBundles"] = signing.RequireSignedBundles,
["signingEnabled"] = signing.Enabled,
["trustedKeys"] = signing.TrustedKeys.Count,
};
var advisory = BundleSigningPolicyAdvisory.Evaluate(signing);
return Task.FromResult(advisory is null
? HealthCheckResult.Healthy("Bundle signing policy OK.", data)
: HealthCheckResult.Degraded(advisory, data: data));
}
}