Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Read keyvault secrets from app config json
//1. Create a file for both classes
//2. Replace the IKeyVaultAccessClient with a concrete keyvault implementation
//3. Add it to the builder: builder.Add(new ReplaceTokensConfigurationSource(configuration, logger, client));
//4. Add secrets placeholder to the appsetings.json with the following pattern __secret__
/// <summary>
/// A JSON configuration source that replaces values from JSON that are tokenized with a value from Azure Key Vault,
/// using the tokenized value as lookup key.
/// </summary>
public sealed class ReplaceTokensConfigurationProvider : ConfigurationProvider, IDisposable
{
private readonly Regex _regex;
private readonly ILogger _logger;
private readonly IConfiguration _config;
private readonly IKeyVaultAccessClient _keyVaultClient;
public const string TokenStartMarker = "__";
public const string TokenEndMarker = TokenStartMarker;
private readonly System.Runtime.Caching.MemoryCache _cache = new System.Runtime.Caching.MemoryCache("cache");
/// <summary>
/// Creates a new instance.
/// </summary>
public ReplaceTokensConfigurationProvider([NotNull] IConfiguration config,
[NotNull] ILogger logger,
[NotNull] IKeyVaultAccessClient keyVaultClient)
{
_config = config ?? throw new ArgumentNullException(nameof(config));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_keyVaultClient = keyVaultClient ?? throw new ArgumentNullException(nameof(keyVaultClient));
_regex = new Regex($"({TokenStartMarker})(.*?)({TokenEndMarker})", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Singleline);
}
/// <inheritdoc />
public override bool TryGet(string key, out string value)
{
value = _config[key];
if (value == null) return false;
if (_regex.IsMatch(value))
{
_logger.LogTrace("Found tokenized value '{value}' for key '{key}'", value, key);
//value is key in replacement provider
string keyVaultKey = _regex.Split(value)[2];
string secret;
if (!_cache.Contains(keyVaultKey))
{
secret = TryGetSecret(keyVaultKey);
if (secret?.Length > 2)
{
secret = secret.Trim('"');
_logger.LogInformation("Retrieved secret for key '{key}'", key);
_cache.Add(keyVaultKey, secret, DateTimeOffset.UtcNow.AddHours(24));
}
else
{
_logger.LogError("Failed to retrieve secret for key '{key}'", key);
}
}
else
{
secret = (string)_cache.Get(keyVaultKey);
}
if (!string.IsNullOrEmpty(secret))
{
value = _regex.Replace(value, secret);
return true;
}
}
value = null;
return false;
}
private string TryGetSecret(string keyVaultKey)
{
try
{
string secret = _keyVaultClient.GetConfigValue(keyVaultKey)
.ConfigureAwait(false)
.GetAwaiter()
.GetResult();
return secret;
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to get secret from Azure Key Vault wrapper service.");
}
return null;
}
/// <inheritdoc />
public void Dispose()
{
_cache.Dispose();
}
}
/// <summary>
/// Represents a JSON file as an <see cref="IConfigurationSource" />.
/// </summary>
public class ReplaceTokensConfigurationSource : FileConfigurationSource
{
private readonly ILogger _logger;
private readonly IKeyVaultAccessClient _keyVaultClient;
private readonly IConfiguration _config;
public ReplaceTokensConfigurationSource(IConfiguration config, ILogger logger, IKeyVaultAccessClient keyVaultClient)
{
_config = config;
_logger = logger;
_keyVaultClient = keyVaultClient;
}
/// <summary>
/// Builds the <see cref="ReplaceTokensConfigurationProvider" /> for this source.
/// </summary>
/// <param name="builder">The <see cref="IConfigurationBuilder" />.</param>
/// <returns>A <see cref="ReplaceTokensConfigurationProvider" /></returns>
public override IConfigurationProvider Build(IConfigurationBuilder builder)
{
EnsureDefaults(builder);
return new ReplaceTokensConfigurationProvider(_config, _logger, _keyVaultClient);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.