fix: redact bot secrets in startup logs
This commit is contained in:
@@ -0,0 +1,47 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Npgsql;
|
||||||
|
|
||||||
|
namespace GmRelay.Bot.Infrastructure.Logging;
|
||||||
|
|
||||||
|
public static partial class SecretRedactor
|
||||||
|
{
|
||||||
|
public static string RedactConnectionString(string? connectionString)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(connectionString))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var builder = new NpgsqlConnectionStringBuilder(connectionString);
|
||||||
|
if (!string.IsNullOrWhiteSpace(builder.Password))
|
||||||
|
{
|
||||||
|
builder.Password = "***";
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
|
return RedactText(connectionString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string RedactText(string? text)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(text))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SecretKeyValueRegex().Replace(
|
||||||
|
text,
|
||||||
|
static match => $"{match.Groups["key"].Value}={GetRedactedValue()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetRedactedValue() => "***";
|
||||||
|
|
||||||
|
[GeneratedRegex(@"(?<key>password|pwd|passwd|token|secret|api[-_]?key)\s*=\s*(?<value>[^;\s,]+)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
|
||||||
|
private static partial Regex SecretKeyValueRegex();
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ using GmRelay.Bot.Features.Reminders.SendJoinLink;
|
|||||||
using GmRelay.Bot.Features.Sessions.CreateSession;
|
using GmRelay.Bot.Features.Sessions.CreateSession;
|
||||||
using GmRelay.Bot.Features.Sessions.RescheduleSession;
|
using GmRelay.Bot.Features.Sessions.RescheduleSession;
|
||||||
using GmRelay.Bot.Infrastructure.Database;
|
using GmRelay.Bot.Infrastructure.Database;
|
||||||
|
using GmRelay.Bot.Infrastructure.Logging;
|
||||||
using GmRelay.Bot.Infrastructure.Scheduling;
|
using GmRelay.Bot.Infrastructure.Scheduling;
|
||||||
using GmRelay.Bot.Infrastructure.Telegram;
|
using GmRelay.Bot.Infrastructure.Telegram;
|
||||||
using Npgsql;
|
using Npgsql;
|
||||||
@@ -20,11 +21,16 @@ builder.AddServiceDefaults();
|
|||||||
builder.Services.AddSingleton<NpgsqlDataSource>(sp =>
|
builder.Services.AddSingleton<NpgsqlDataSource>(sp =>
|
||||||
{
|
{
|
||||||
var config = sp.GetRequiredService<IConfiguration>();
|
var config = sp.GetRequiredService<IConfiguration>();
|
||||||
|
var loggerFactory = sp.GetRequiredService<ILoggerFactory>();
|
||||||
var connectionString = config.GetConnectionString("gmrelaydb")
|
var connectionString = config.GetConnectionString("gmrelaydb")
|
||||||
?? throw new InvalidOperationException(
|
?? throw new InvalidOperationException(
|
||||||
"ConnectionStrings:gmrelaydb is required. Set via environment variable ConnectionStrings__gmrelaydb.");
|
"ConnectionStrings:gmrelaydb is required. Set via environment variable ConnectionStrings__gmrelaydb.");
|
||||||
|
|
||||||
Console.WriteLine($"[DBG] Master ConnectionString => {connectionString}");
|
var logger = loggerFactory.CreateLogger("GmRelay.Bot.Startup");
|
||||||
|
logger.LogInformation(
|
||||||
|
"Configured PostgreSQL data source with connection string {ConnectionString}",
|
||||||
|
SecretRedactor.RedactConnectionString(connectionString));
|
||||||
|
|
||||||
return NpgsqlDataSource.Create(connectionString);
|
return NpgsqlDataSource.Create(connectionString);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using GmRelay.Bot.Infrastructure.Logging;
|
||||||
|
|
||||||
|
namespace GmRelay.Bot.Tests.Infrastructure.Logging;
|
||||||
|
|
||||||
|
public sealed class SecretRedactorTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void RedactConnectionString_ShouldMaskDatabasePassword()
|
||||||
|
{
|
||||||
|
var result = SecretRedactor.RedactConnectionString(
|
||||||
|
"Host=localhost;Port=5432;Database=gmrelay;Username=gmrelay;Password=super-secret");
|
||||||
|
|
||||||
|
Assert.Contains("Password=***", result);
|
||||||
|
Assert.DoesNotContain("super-secret", result);
|
||||||
|
Assert.Contains("Host=localhost", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RedactText_ShouldMaskKnownSecretKeys()
|
||||||
|
{
|
||||||
|
var result = SecretRedactor.RedactText(
|
||||||
|
"Password=super-secret Token=telegram-token apiKey=service-key");
|
||||||
|
|
||||||
|
Assert.DoesNotContain("super-secret", result);
|
||||||
|
Assert.DoesNotContain("telegram-token", result);
|
||||||
|
Assert.DoesNotContain("service-key", result);
|
||||||
|
Assert.Contains("Password=***", result);
|
||||||
|
Assert.Contains("Token=***", result);
|
||||||
|
Assert.Contains("apiKey=***", result);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user