The Daily Parker

Politics, Weather, Photography, and the Dog

How to pass secrets into your custom log target

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.

Comments are closed