feat(secrets)!: retire the gRPC secrets hub in ScadaBridge — sites get secrets via Deploy Artifacts; rig central on the shared SQL store
This commit is contained in:
@@ -33,8 +33,6 @@ using ZB.MOM.WW.ScadaBridge.Transport;
|
||||
using ZB.MOM.WW.Secrets.Abstractions;
|
||||
using ZB.MOM.WW.Secrets.Configuration;
|
||||
using ZB.MOM.WW.Secrets.DependencyInjection;
|
||||
using ZB.MOM.WW.Secrets.Replicator.SqlServer;
|
||||
using ZB.MOM.WW.Secrets.Replicator.SqlServer.DependencyInjection;
|
||||
using ZB.MOM.WW.Secrets.Sqlite;
|
||||
using ZB.MOM.WW.Secrets.Ui;
|
||||
using ZB.MOM.WW.Telemetry;
|
||||
@@ -77,31 +75,11 @@ var configuration = new ConfigurationBuilder()
|
||||
// Expand ${secret:...} config references before any validator/binder sees them (Layer A).
|
||||
// Throwaway provider — disposed here, shares no singletons with the host container.
|
||||
//
|
||||
// The expander must read the SAME store the running node will serve. On a central node in Grpc
|
||||
// replication mode that store is the SHARED SQL-Server store (see SecretsRegistration,
|
||||
// scadaproj#4) — an expander left on SQLite there would resolve pre-host ${secret:} references
|
||||
// from a stale/empty local store, silently diverging from what the node's own hub serves. Every
|
||||
// other case (sites, SqlServer mode, replication off) keeps the local SQLite path exactly as it
|
||||
// always was. A blank or ${secret:}-valued connection string fails HERE, through the same
|
||||
// EnsureCentralSharedStoreConnectionString check AddScadaBridgeSecrets runs — this is the
|
||||
// earliest code that would otherwise hand the bad value to SqlConnection, whose generic
|
||||
// "initialization string" format error would bury the designed message.
|
||||
// This always reads the node's LOCAL SQLite store: Task 11 retired the gRPC secrets hub, the only
|
||||
// topology that ever put a central node's store somewhere else pre-host. (Secrets:Store:Provider
|
||||
// =SqlServer, the still-supported shared-central-store feature, is a post-host DI registration —
|
||||
// see SecretsRegistration — and does not change what the Layer-A expander itself reads.)
|
||||
//
|
||||
// Two consequences worth knowing. This block runs BEFORE Serilog exists and outside the
|
||||
// try/catch below, so a SQL Server outage at central boot exits with a bare stderr stack
|
||||
// trace, no structured log — honest and container-restart-retryable, just unenriched; the
|
||||
// SQLite path had the same window but its only failure mode was a local file. (That exit
|
||||
// is real only because of the UnhandledException handler at the top of this file — the
|
||||
// runtime's own crash path wedges as container PID 1, ScadaBridge#34.) And the
|
||||
// role check is deliberately NOT folded into SecretsRegistration: that class refuses to
|
||||
// read the node role from configuration (a config-read role could silently turn a site
|
||||
// into a hub — see SecretsNodeRole), while Program.cs must read it to branch the whole
|
||||
// composition anyway, so the config read stays here at the composition root.
|
||||
var expanderUsesSharedSqlStore =
|
||||
string.Equals(
|
||||
configuration["ScadaBridge:Node:Role"], "Central", StringComparison.OrdinalIgnoreCase)
|
||||
&& SecretsRegistration.UsesGrpcHub(configuration);
|
||||
|
||||
// Secrets 0.6.2 validates Secrets:SqlitePath as absolute AND outside the content root, finding the
|
||||
// content root from IHostEnvironment — which this throwaway ServiceCollection does not have, and
|
||||
// which does not exist yet anyway (the host is not built until further down). Both the resolution
|
||||
@@ -109,32 +87,15 @@ var expanderUsesSharedSqlStore =
|
||||
// AddPreHostSqliteExpander for why the content-root argument is load-bearing and silent when
|
||||
// dropped, and PreHostSecretsContentRootTests for the regression guard.
|
||||
var expanderServices = new ServiceCollection();
|
||||
if (expanderUsesSharedSqlStore)
|
||||
{
|
||||
SecretsRegistration.EnsureCentralSharedStoreConnectionString(
|
||||
configuration, $"{SecretsRegistration.ReplicationModeKey}=Grpc");
|
||||
expanderServices.AddZbSecretsSqlServerStore(configuration, "Secrets");
|
||||
}
|
||||
else
|
||||
{
|
||||
expanderServices.AddPreHostSqliteExpander(
|
||||
configuration, SecretsRegistration.ResolveHostContentRoot());
|
||||
}
|
||||
expanderServices.AddPreHostSqliteExpander(
|
||||
configuration, SecretsRegistration.ResolveHostContentRoot());
|
||||
|
||||
#pragma warning disable ASP0000 // deliberate throwaway container
|
||||
await using (var secretsProvider = expanderServices.BuildServiceProvider())
|
||||
#pragma warning restore ASP0000
|
||||
{
|
||||
if (expanderUsesSharedSqlStore)
|
||||
{
|
||||
await secretsProvider.GetRequiredService<SqlServerSecretsStoreMigrator>()
|
||||
.MigrateAsync(default);
|
||||
}
|
||||
else
|
||||
{
|
||||
await secretsProvider.GetRequiredService<SqliteSecretsStoreMigrator>()
|
||||
.MigrateAsync(default);
|
||||
}
|
||||
await secretsProvider.GetRequiredService<SqliteSecretsStoreMigrator>()
|
||||
.MigrateAsync(default);
|
||||
|
||||
var resolver = secretsProvider.GetRequiredService<ISecretResolver>();
|
||||
await new SecretReferenceExpander(resolver)
|
||||
@@ -740,21 +701,9 @@ try
|
||||
// connection to the right pipeline by listener/protocol.
|
||||
app.MapGrpcService<ZB.MOM.WW.ScadaBridge.Communication.Grpc.CentralControlGrpcService>();
|
||||
|
||||
// The pull-only secrets hub (scadaproj#3), on the SAME h2c listener and the same addressing
|
||||
// convention — a site dials central's CentralGrpcPort, never central's database. It shares
|
||||
// the listener with CentralControlService the way the site's LocalDb sync endpoint shares
|
||||
// its one: two disjoint service prefixes, two independent fail-closed gates. The
|
||||
// CentralControlAuthInterceptor registered on AddGrpc is prefix-scoped to
|
||||
// CentralControlService and passes hub calls straight through; the hub's own
|
||||
// SecretsHubAuthInterceptor — attached per-service by AddZbSecretsGrpcHub — is what gates
|
||||
// it, on the shared Secrets:GrpcHub:BearerToken.
|
||||
//
|
||||
// A NO-OP unless this node registered the hub. That is not an optimisation: mapping the
|
||||
// service without registering it would map a hub whose interceptor was never attached,
|
||||
// i.e. an unauthenticated endpoint serving every secret central holds. Both this call and
|
||||
// the registration above route through SecretsRegistration.UsesGrpcHub so they cannot
|
||||
// drift apart.
|
||||
app.MapScadaBridgeSecretsHub(app.Configuration);
|
||||
// The pull-only secrets hub that used to be mapped here (scadaproj#3) is retired — Task 11.
|
||||
// Sites receive secrets exclusively via Deploy Artifacts; there is no hub endpoint to map
|
||||
// on central and nothing left for a site to sweep. See SecretsRegistration.
|
||||
|
||||
app.MapStaticAssets();
|
||||
app.MapCentralUI<ZB.MOM.WW.ScadaBridge.Host.Components.App>();
|
||||
|
||||
Reference in New Issue
Block a user