fix(security): pin the cached-key expiry guard, meter cache_hit after it, and close two wiring gaps (review fixes)
This commit is contained in:
@@ -108,6 +108,10 @@ public sealed class BoundedApiKeyStore : IApiKeyStore
|
||||
&& TryServe(_keys, apiKeyId, ttl, out var cached)
|
||||
&& !IsExpired(cached, utcNow))
|
||||
{
|
||||
// Metered HERE rather than inside TryServe: an entry that is inside the TTL but whose key has
|
||||
// since expired is not a hit — it falls through to a store read — so counting it in TryServe
|
||||
// would report cache hits that never happened.
|
||||
MeterCacheHit();
|
||||
return cached;
|
||||
}
|
||||
|
||||
@@ -122,7 +126,11 @@ public sealed class BoundedApiKeyStore : IApiKeyStore
|
||||
public async Task<IReadOnlyList<NodeAcl>> FindGrantsAsync(string principal, CancellationToken ct)
|
||||
{
|
||||
var ttl = Ttl;
|
||||
if (ttl > TimeSpan.Zero && TryServe(_grants, principal, ttl, out var cached)) return cached;
|
||||
if (ttl > TimeSpan.Zero && TryServe(_grants, principal, ttl, out var cached))
|
||||
{
|
||||
MeterCacheHit();
|
||||
return cached;
|
||||
}
|
||||
|
||||
var fresh = await BoundAsync(token => _inner.FindGrantsAsync(principal, token), ct)
|
||||
.ConfigureAwait(false);
|
||||
@@ -140,6 +148,10 @@ public sealed class BoundedApiKeyStore : IApiKeyStore
|
||||
private static bool IsExpired(ApiKeyLookup? cached, DateTime utcNow) =>
|
||||
cached?.Key.ExpiresUtc is { } expires && expires <= utcNow;
|
||||
|
||||
/// <summary>Counts one lookup the cache actually answered.</summary>
|
||||
private static void MeterCacheHit() =>
|
||||
OtOpcUaTelemetry.ApiKeyStoreBounded.Add(1, new KeyValuePair<string, object?>("outcome", "cache_hit"));
|
||||
|
||||
/// <summary>Serves an entry if one is present and still inside the TTL.</summary>
|
||||
/// <typeparam name="T">The cached value type.</typeparam>
|
||||
/// <param name="cache">The cache to read.</param>
|
||||
@@ -152,8 +164,6 @@ public sealed class BoundedApiKeyStore : IApiKeyStore
|
||||
{
|
||||
if (cache.TryGetValue(key, out var entry) && _time.GetUtcNow() - entry.StampedUtc < ttl)
|
||||
{
|
||||
OtOpcUaTelemetry.ApiKeyStoreBounded.Add(
|
||||
1, new KeyValuePair<string, object?>("outcome", "cache_hit"));
|
||||
value = entry.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -307,12 +307,19 @@ public sealed class OtOpcUaServerHostedService : IHostedService, IAsyncDisposabl
|
||||
/// </para>
|
||||
/// <para>Separate from <see cref="StartAsync"/> so the timer's lifecycle is testable without booting the SDK.</para>
|
||||
/// </remarks>
|
||||
internal void StartRevalidationTimer() =>
|
||||
internal void StartRevalidationTimer()
|
||||
{
|
||||
// Dispose before overwriting. StartAsync runs again when a host restarts the service around the
|
||||
// same instance, and an overwritten field would leave the previous timer armed with nothing left
|
||||
// to dispose it — the same dispose-before-overwrite rule the driver reconnect paths carry.
|
||||
_revalidationTimer?.Dispose();
|
||||
|
||||
_revalidationTimer = new AclRevalidationTimer(
|
||||
() => _server?.NodeManager?.RevalidateMonitoredItems(),
|
||||
TimeSpan.FromSeconds(_aclOptions.RevalidationIntervalSeconds),
|
||||
_time,
|
||||
_loggerFactory.CreateLogger<AclRevalidationTimer>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the OPC UA server asynchronously.
|
||||
|
||||
Reference in New Issue
Block a user