Today I finally solved a problem that has nagged me for months: Given a .NET Core 3.1 Web API, NLog, and a custom log target for NLog, how could I pass secrets into the log target using Azure Key Vault?
The last bit required me to add Azure Key Vault support to the InnerDrive.Logging NuGet package, which I did back in February. The KeyVaultSecretProvider
class allows you to retrieve secrets from a specified Azure Key Vault. (I'm in the process of updating it to handle keys as well.) Before that, you'd have to put your secrets in configuration files, which anyone on your development team or who has access to your Git repository can see. For example, to use our SendGridTarget
class, you would have to put this in your nLog.config
file:
<extensions>
<add assembly="NLog.Web.AspNetCore"/>
<add assembly="InnerDrive.Logging"/>
</extensions>
<targets async="false">
<target xsi:type="SendGridTarget"
name="sendgrid"
apiKey="{supposedly secret key}"
applicationName="My Cool App"
from="service@contoso.org"
to="admin@contoso.org"
/>
</targets>
That is...suboptimal. Instead, you want to use a class that implements ISecretProvider
and inject it into the SendGridTarget
class through this constructor:
/// <summary>
/// Creates a new instance of <see cref="SendGridTarget"/> with
/// a specified <see cref="ISendGridSender"/> implementation.
/// </summary>
/// <param name="sender">The <see cref="ISendGridSender"/> to use</param>
/// <param name="secretProvider">The <see cref="IConfiguration"/> to use</param>
/// <param name="apiKey">The API key to use</param>
public SendGridTarget(
ISendGridSender sender,
ISecretProvider secretProvider = null,
string apiKey = null)
{
IncludeEventProperties = true;
if (null != secretProvider) Configuration = secretProvider;
if (!string.IsNullOrWhiteSpace(apiKey)) ApiKey = apiKey;
_sender = sender;
}
And now I'm going to save you about 30 hours of research and frustration. To get NLog to inject the secret provider into the target, I added this method to Startup.cs
:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IComponentContext container)
{
// Setup code elided
ConfigureLogging(container);
}
public static void ConfigureLogging(IComponentContext container)
{
var defaultConstructor = ConfigurationItemFactory.Default.CreateInstance;
var secretProvider = container.Resolve<ISecretProvider>();
ConfigurationItemFactory.Default.CreateInstance = type =>
{
if (type == typeof(SendGridTarget))
{
var sendGridSender = container.Resolve<ISendGridSender>();
return new SendGridTarget(sendGridSender, secretProvider);
}
return defaultConstructor(type);
};
LogManager.Configuration = LogManager.Configuration.Reload();
NLogBuilder.ConfigureNLog(LogManager.Configuration);
}
(Note that the ISecretProvider
and ISendGridSender
classes need to be registered with your dependency-injection framework.)
Today was a good day.