Last active
January 9, 2019 20:30
-
-
Save MarcBruins/9aebc0ac4f5d257ac4cbc3dcef46e4a4 to your computer and use it in GitHub Desktop.
Read keyvault secrets from app config json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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