fix(site-secrets): one shared secret: reference parser for scanner and MxGateway adapter
Final review M3: SecretReferenceScanner.TryParseSecretReference (trimmed, case-insensitive
prefix, trimmed name, empty = not a reference, ${secret:} never matches) is used by the scanner
and by MxGatewayDataConnection's ApiKey resolution, so a padded or case-variant reference
resolves the name central ships. A bare 'secret:' ApiKey now fails closed. M7: unit test pins
stamped hash == ComputeCurrentArtifactHashesAsync with a non-empty Secrets list.
This commit is contained in:
@@ -17,6 +17,29 @@ public static class SecretReferenceScanner
|
||||
public const string Prefix = "secret:";
|
||||
private static readonly string[] BootstrapPrefixes = ["SB-GRPC-PSK-"];
|
||||
|
||||
/// <summary>
|
||||
/// The single parser for a <c>secret:<name></c> reference value, shared by this scanner
|
||||
/// (what central ships) and every adapter that resolves the reference at connect time (what
|
||||
/// the site looks up), so both sides always agree on the name (final review M3). The value is
|
||||
/// trimmed, the <c>secret:</c> prefix matches ordinal-ignore-case, and the remainder is
|
||||
/// trimmed; an empty remainder is not a reference. The config-expander form
|
||||
/// <c>${secret:…}</c> is a different syntax and never matches.
|
||||
/// </summary>
|
||||
/// <param name="value">A configuration string value.</param>
|
||||
/// <param name="name">The referenced secret name when this returns <see langword="true"/>; otherwise empty.</param>
|
||||
/// <returns><see langword="true"/> when <paramref name="value"/> is a <c>secret:<name></c> reference.</returns>
|
||||
public static bool TryParseSecretReference(string? value, out string name)
|
||||
{
|
||||
name = string.Empty;
|
||||
if (value is null) return false;
|
||||
var trimmed = value.Trim();
|
||||
if (!trimmed.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase)) return false;
|
||||
var candidate = trimmed[Prefix.Length..].Trim();
|
||||
if (candidate.Length == 0) return false;
|
||||
name = candidate;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scans every connection's primary and backup configuration JSON for <c>secret:<name></c>
|
||||
/// string values and returns the distinct names found, ordinal-sorted, each with the ordinal-sorted,
|
||||
@@ -51,10 +74,8 @@ public static class SecretReferenceScanner
|
||||
case JsonValueKind.Object: foreach (var p in e.EnumerateObject()) stack.Push(p.Value); break;
|
||||
case JsonValueKind.Array: foreach (var i in e.EnumerateArray()) stack.Push(i); break;
|
||||
case JsonValueKind.String:
|
||||
var s = e.GetString()!.Trim();
|
||||
if (!s.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase)) break;
|
||||
var name = s[Prefix.Length..].Trim();
|
||||
if (name.Length == 0 || BootstrapPrefixes.Any(b => name.StartsWith(b, StringComparison.OrdinalIgnoreCase))) break;
|
||||
if (!TryParseSecretReference(e.GetString(), out var name)) break;
|
||||
if (BootstrapPrefixes.Any(b => name.StartsWith(b, StringComparison.OrdinalIgnoreCase))) break;
|
||||
yield return name;
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user