fix(configuration-database): resolve ConfigurationDatabase-002..007 — remove hardcoded sa creds, fail-fast no-arg DI, encrypt secret columns, resilient audit serialization

This commit is contained in:
Joseph Doherty
2026-05-16 21:11:24 -04:00
parent 8fc04d43c2
commit 0c82ffcbe6
17 changed files with 2029 additions and 40 deletions

View File

@@ -8,6 +8,19 @@ public class AuditService : IAuditService
{
private readonly ScadaLinkDbContext _context;
/// <summary>
/// Serializer options for audit <c>afterState</c> payloads. Audit writes commit in the
/// same transaction as the change they record, so a serialization exception here would
/// roll back the entire business operation. Reference cycles (common when an EF entity
/// with loaded navigations is passed in) are ignored rather than thrown, and depth is
/// bounded so a pathological graph cannot produce an unbounded payload.
/// </summary>
private static readonly JsonSerializerOptions AuditSerializerOptions = new()
{
ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles,
MaxDepth = 32
};
public AuditService(ScadaLinkDbContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
@@ -26,7 +39,7 @@ public class AuditService : IAuditService
{
Timestamp = DateTimeOffset.UtcNow,
AfterStateJson = afterState != null
? JsonSerializer.Serialize(afterState)
? SerializeAfterState(afterState)
: null
};
@@ -34,4 +47,27 @@ public class AuditService : IAuditService
// to ensure atomicity with the entity change.
await _context.AuditLogEntries.AddAsync(entry, cancellationToken);
}
/// <summary>
/// Serializes the caller-supplied after-state, tolerating arbitrary object shapes.
/// Reference cycles are ignored via <see cref="AuditSerializerOptions"/>. If serialization
/// still fails (e.g. <c>MaxDepth</c> exceeded), the audit entry is preserved with a
/// diagnostic placeholder rather than throwing — a serialization failure must never
/// roll back the business operation the audit entry is recording.
/// </summary>
private static string SerializeAfterState(object afterState)
{
try
{
return JsonSerializer.Serialize(afterState, AuditSerializerOptions);
}
catch (Exception ex) when (ex is JsonException or NotSupportedException)
{
return JsonSerializer.Serialize(new
{
AuditSerializationError = ex.Message,
StateType = afterState.GetType().FullName
});
}
}
}