153 lines
4.5 KiB
C#
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)");
|
|
}
|
|
}
|