Files
git.stella-ops.org/src/Scanner/__Libraries/StellaOps.Scanner.Surface.Secrets/Providers/AuditingSurfaceSecretProvider.cs

153 lines
4.5 KiB
C#

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace StellaOps.Scanner.Surface.Secrets.Providers;
/// <summary>
/// Wraps a secret provider with audit logging. Each secret access is logged with tenant, component,
/// secret type, and provider metadata for observability and compliance.
/// </summary>
internal sealed class AuditingSurfaceSecretProvider : ISurfaceSecretProvider
{
private readonly ISurfaceSecretProvider _inner;
private readonly TimeProvider _timeProvider;
private readonly ILogger _logger;
private readonly string _componentName;
public AuditingSurfaceSecretProvider(
ISurfaceSecretProvider inner,
TimeProvider timeProvider,
ILogger logger,
string componentName)
{
_inner = inner ?? throw new ArgumentNullException(nameof(inner));
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_componentName = componentName ?? throw new ArgumentNullException(nameof(componentName));
}
public SurfaceSecretHandle Get(SurfaceSecretRequest request)
{
var startTime = _timeProvider.GetUtcNow();
try
{
var handle = _inner.Get(request);
var elapsed = _timeProvider.GetUtcNow() - startTime;
LogAuditEvent(
request,
handle.Metadata,
success: true,
elapsed,
error: null);
return handle;
}
catch (SurfaceSecretNotFoundException)
{
var elapsed = _timeProvider.GetUtcNow() - startTime;
LogAuditEvent(
request,
metadata: null,
success: false,
elapsed,
error: "NotFound");
throw;
}
catch (Exception ex)
{
var elapsed = _timeProvider.GetUtcNow() - startTime;
LogAuditEvent(
request,
metadata: null,
success: false,
elapsed,
error: ex.GetType().Name);
throw;
}
}
public async ValueTask<SurfaceSecretHandle> GetAsync(
SurfaceSecretRequest request,
CancellationToken cancellationToken = default)
{
var startTime = _timeProvider.GetUtcNow();
try
{
var handle = await _inner.GetAsync(request, cancellationToken).ConfigureAwait(false);
var elapsed = _timeProvider.GetUtcNow() - startTime;
LogAuditEvent(
request,
handle.Metadata,
success: true,
elapsed,
error: null);
return handle;
}
catch (SurfaceSecretNotFoundException)
{
var elapsed = _timeProvider.GetUtcNow() - startTime;
LogAuditEvent(
request,
metadata: null,
success: false,
elapsed,
error: "NotFound");
throw;
}
catch (Exception ex)
{
var elapsed = _timeProvider.GetUtcNow() - startTime;
LogAuditEvent(
request,
metadata: null,
success: false,
elapsed,
error: ex.GetType().Name);
throw;
}
}
private void LogAuditEvent(
SurfaceSecretRequest request,
IReadOnlyDictionary<string, string>? metadata,
bool success,
TimeSpan elapsed,
string? error)
{
// Structured log entry for audit trail. NEVER log secret contents.
_logger.Log(
success ? LogLevel.Information : LogLevel.Warning,
"Surface secret access: " +
"Component={Component}, " +
"Tenant={Tenant}, " +
"RequestComponent={RequestComponent}, " +
"SecretType={SecretType}, " +
"Name={Name}, " +
"Success={Success}, " +
"ElapsedMs={ElapsedMs}, " +
"Provider={Provider}, " +
"Error={Error}",
_componentName,
request.Tenant,
request.Component,
request.SecretType,
request.Name ?? "default",
success,
elapsed.TotalMilliseconds,
metadata?.GetValueOrDefault("source") ?? "unknown",
error ?? "(none)");
}
}