feat(secrets): add ZB.MOM.WW.Secrets refs + pre-host ${secret:} expander (Tasks 1-2)
Adopt the ZB.MOM.WW.Secrets library (0.1.2, Gitea feed) into OtOpcUa under CPM:
- Directory.Packages.props + NuGet.config source mapping for the three packages
- Host/AdminUI/Driver.Galaxy.Contracts/Driver.OpcUaClient package references
- Layer-A pre-host ${secret:} config expander in Host/Program.cs, placed after all
config providers assemble and before the first config read (mechanism only,
behavior-neutral no-op until Task 4 introduces tokens)
- Secrets section in appsettings.json (env-var KEK, SQLite store, 30s cache TTL)
Mechanism mirrors the proven HistorianGateway adoption. KEK never committed.
This commit is contained in:
@@ -37,6 +37,11 @@ using ZB.MOM.WW.OtOpcUa.Security.Ldap;
|
||||
using ZB.MOM.WW.Configuration;
|
||||
using ZB.MOM.WW.Telemetry.Serilog;
|
||||
using ZB.MOM.WW.OtOpcUa.AdminUI.Api;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ZB.MOM.WW.Secrets.Abstractions;
|
||||
using ZB.MOM.WW.Secrets.Configuration;
|
||||
using ZB.MOM.WW.Secrets.DependencyInjection;
|
||||
using ZB.MOM.WW.Secrets.Sqlite;
|
||||
|
||||
// Roles drive the entire conditional wiring below — see ZB.MOM.WW.OtOpcUa.Cluster.RoleParser.
|
||||
var roles = RoleParser.Parse(Environment.GetEnvironmentVariable("OTOPCUA_ROLES"));
|
||||
@@ -72,6 +77,27 @@ if (roleSuffix is not null)
|
||||
builder.Configuration.AddCommandLine(args);
|
||||
}
|
||||
|
||||
// Pre-host ${secret:} expansion: rewrites ${secret:name} tokens in the assembled configuration
|
||||
// BEFORE the host is built, so every downstream binder/validator sees resolved plaintext.
|
||||
// Placement matters: this runs AFTER all config providers are assembled (base appsettings, the
|
||||
// role overlay, and the env-vars + command-line re-append above) but BEFORE anything READS a
|
||||
// config value (AddZbSerilog's ReadFrom.Configuration, AddOtOpcUaConfigDb, and the first
|
||||
// ValidateOnStart bind all follow). With no ${secret:} tokens present this is a no-op pass;
|
||||
// it also migrates the secrets SQLite store on startup.
|
||||
// ASP0000: DELIBERATE throwaway container — expansion must run before builder.Build(), so it
|
||||
// cannot use the app's DI. Fully disposed here; shares no singletons with the host container.
|
||||
#pragma warning disable ASP0000
|
||||
await using (var secretsProvider = new ServiceCollection()
|
||||
.AddZbSecrets(builder.Configuration, "Secrets")
|
||||
.BuildServiceProvider())
|
||||
#pragma warning restore ASP0000
|
||||
{
|
||||
await secretsProvider.GetRequiredService<SqliteSecretsStoreMigrator>().MigrateAsync(default);
|
||||
var resolver = secretsProvider.GetRequiredService<ISecretResolver>();
|
||||
await new SecretReferenceExpander(resolver)
|
||||
.ExpandConfigurationAsync((IConfigurationRoot)builder.Configuration, default);
|
||||
}
|
||||
|
||||
// Anchor the process working directory to the install directory (AppContext.BaseDirectory) so every
|
||||
// relative runtime path resolves under the install dir rather than the service's startup CWD. A Windows
|
||||
// service starts with CWD=C:\Windows\System32, which otherwise scattered the Serilog rolling-file sink
|
||||
|
||||
Reference in New Issue
Block a user