feat(secrets): resolve MxGateway ApiKey secret: refs at connect time (ScadaBridge G-3 T9)
This commit is contained in:
@@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Protocol;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Serialization;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
||||
using ZB.MOM.WW.Secrets.Abstractions;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Adapters;
|
||||
|
||||
@@ -24,6 +25,7 @@ public class MxGatewayDataConnection : IDataConnection, IBrowsableDataConnection
|
||||
{
|
||||
private readonly IMxGatewayClientFactory _clientFactory;
|
||||
private readonly ILogger<MxGatewayDataConnection> _logger;
|
||||
private readonly ISecretResolver? _secretResolver;
|
||||
private IMxGatewayClient? _client;
|
||||
private ConnectionHealth _status = ConnectionHealth.Disconnected;
|
||||
private CancellationTokenSource? _eventLoopCts;
|
||||
@@ -51,10 +53,19 @@ public class MxGatewayDataConnection : IDataConnection, IBrowsableDataConnection
|
||||
/// <summary>Initializes a new instance of <see cref="MxGatewayDataConnection"/>.</summary>
|
||||
/// <param name="clientFactory">Factory used to create gateway client instances.</param>
|
||||
/// <param name="logger">Logger instance.</param>
|
||||
public MxGatewayDataConnection(IMxGatewayClientFactory clientFactory, ILogger<MxGatewayDataConnection> logger)
|
||||
/// <param name="secretResolver">
|
||||
/// Optional secret resolver used to resolve a <c>secret:<name></c> ApiKey reference at
|
||||
/// connect time. When null, a literal ApiKey still works, but a <c>secret:</c> reference
|
||||
/// fails closed (a connection is never established with an unresolved key).
|
||||
/// </param>
|
||||
public MxGatewayDataConnection(
|
||||
IMxGatewayClientFactory clientFactory,
|
||||
ILogger<MxGatewayDataConnection> logger,
|
||||
ISecretResolver? secretResolver = null)
|
||||
{
|
||||
_clientFactory = clientFactory;
|
||||
_logger = logger;
|
||||
_secretResolver = secretResolver;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -68,6 +79,25 @@ public class MxGatewayDataConnection : IDataConnection, IBrowsableDataConnection
|
||||
{
|
||||
var cfg = MxGatewayEndpointConfigSerializer.FromFlatDict(connectionDetails);
|
||||
|
||||
// Resolve a `secret:<name>` ApiKey reference BEFORE any teardown/client creation
|
||||
// so a fail-closed throw here leaves adapter state untouched (no half-open client,
|
||||
// previous connection — if any — still intact for a later retry). A literal key
|
||||
// (no `secret:` prefix) passes through unchanged for back-compat.
|
||||
var apiKey = cfg.ApiKey;
|
||||
if (!string.IsNullOrEmpty(apiKey) &&
|
||||
apiKey.StartsWith("secret:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (_secretResolver is null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"MxGateway ApiKey uses a 'secret:' reference but no ISecretResolver is available to resolve it.");
|
||||
}
|
||||
var secretName = apiKey["secret:".Length..];
|
||||
apiKey = await _secretResolver.GetAsync(new SecretName(secretName), cancellationToken)
|
||||
?? throw new InvalidOperationException(
|
||||
$"MxGateway ApiKey secret '{secretName}' could not be resolved (not found or tombstoned).");
|
||||
}
|
||||
|
||||
// Reconnect reuses this adapter: cancel the previous event loop and dispose
|
||||
// the previous client so a flapping gateway cannot accumulate orphaned
|
||||
// streams/sockets, and the old loop's fault path cannot signal Disconnected
|
||||
@@ -93,7 +123,7 @@ public class MxGatewayDataConnection : IDataConnection, IBrowsableDataConnection
|
||||
|
||||
await _client.ConnectAsync(new MxGatewayConnectionOptions(
|
||||
cfg.Endpoint,
|
||||
cfg.ApiKey,
|
||||
apiKey,
|
||||
string.IsNullOrWhiteSpace(cfg.ClientName) ? "scadabridge" : cfg.ClientName,
|
||||
cfg.WriteUserId,
|
||||
cfg.UseTls,
|
||||
|
||||
Reference in New Issue
Block a user