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:
@@ -0,0 +1,49 @@
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
namespace ScadaLink.ConfigurationDatabase;
|
||||
|
||||
/// <summary>
|
||||
/// EF Core value converter that encrypts a string column at rest using ASP.NET
|
||||
/// Data Protection. Plaintext is protected when written to the database and
|
||||
/// transparently unprotected when read back, so secret-bearing columns
|
||||
/// (SMTP credentials, external-system auth config, database connection strings)
|
||||
/// are never persisted verbatim.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The protector is purpose-scoped so ciphertext from one column cannot be
|
||||
/// unprotected as another. Data Protection keys are persisted to the
|
||||
/// configuration database itself (see <see cref="ScadaLinkDbContext"/> implementing
|
||||
/// <c>IDataProtectionKeyContext</c>), so all central nodes share the same key ring
|
||||
/// and can decrypt each other's writes.
|
||||
/// </remarks>
|
||||
public sealed class EncryptedStringConverter : ValueConverter<string?, string?>
|
||||
{
|
||||
/// <summary>The Data Protection purpose string shared by all encrypted configuration columns.</summary>
|
||||
public const string ProtectorPurpose = "ScadaLink.ConfigurationDatabase.EncryptedColumn";
|
||||
|
||||
public EncryptedStringConverter(IDataProtector protector)
|
||||
: base(
|
||||
plaintext => plaintext == null ? null : protector.Protect(plaintext),
|
||||
ciphertext => ciphertext == null ? null : Unprotect(protector, ciphertext))
|
||||
{
|
||||
}
|
||||
|
||||
private static string Unprotect(IDataProtector protector, string ciphertext)
|
||||
{
|
||||
// A row that predates encryption (or test fixtures inserting raw text) is not valid
|
||||
// protected payload. Unprotect throws CryptographicException in that case; surface a
|
||||
// clearer message rather than a bare crypto failure.
|
||||
try
|
||||
{
|
||||
return protector.Unprotect(ciphertext);
|
||||
}
|
||||
catch (System.Security.Cryptography.CryptographicException ex)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Failed to decrypt an encrypted configuration column. The Data Protection key " +
|
||||
"ring may be unavailable, or the stored value was not written by this system.",
|
||||
ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user