Skip to content

Commit 47b0a79

Browse files
StephenMolloyHongGit
authored andcommitted
Issue 5 (#7)
* Add versioning, optional preloading, and don't stop after preloading one page of secret keys. * Reduce repetitiveness of exception message.
1 parent 6681521 commit 47b0a79

2 files changed

Lines changed: 38 additions & 5 deletions

File tree

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ There are three additional configuration attributes for this config builder:
101101
(vaultName="MyVaultName" |
102102
uri="https://MyVaultName.vault.azure.net")
103103
[connectionString="connection string"]
104+
[version="secrets version"]
105+
[preloadSecretNames="true"]
104106
type="Microsoft.Configuration.ConfigurationBuilders.AzureKeyVaultConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Azure" />
105107
```
106108
If your secrets are kept in Azure Key Vault, then this config builder is for you. There are three additional attributes for this config builder. The `vaultName` is
@@ -110,6 +112,10 @@ up connection information from the execution environment if possible, but you ca
110112
* `vaultName` - This is a required attribute. It specifies the name of the vault in your Azure subscription from which to read key/value pairs.
111113
* `connectionString` - A connection string usable by [AzureServiceTokenProvider](https://docs.microsoft.com/en-us/azure/key-vault/service-to-service-authentication#connection-string-support)
112114
* `uri` - Connect to other Key Vault providers with this attribute. If not specified, Azure is the assumed Vault provider. If the uri _is_specified, then `vaultName` is no longer a required parameter.
115+
* `version` - Azure Key Vault provides a versioning feature for secrets. If this is specified, the builder will only retrieve secrets matching this version.
116+
* `preloadSecretNames` - By default, this builder will query __all__ the key names in the key vault when it is initialized. If this is a concern, set
117+
this attribute to 'false', and secrets will be retrieved one at a time. This could also be useful if the vault allows "Get" access but not
118+
"List" access. (NOTE: Disabling preload is incompatible with Greedy mode.)
113119

114120
### SimpleJsonConfigBuilder
115121
```xml

src/Azure/AzureKeyVaultConfigBuilder.cs

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@ public class AzureKeyVaultConfigBuilder : KeyValueConfigBuilder
1919
public const string vaultNameTag = "vaultName";
2020
public const string connectionStringTag = "connectionString";
2121
public const string uriTag = "uri";
22+
public const string versionTag = "version";
23+
public const string preloadTag = "preloadSecretNames";
2224

2325
private string _vaultName;
2426
private string _connectionString;
2527
private string _uri;
28+
private string _version;
29+
private bool _preload;
2630

2731
private KeyVaultClient _kvClient;
2832
private List<string> _allKeys;
@@ -31,8 +35,14 @@ public override void Initialize(string name, NameValueCollection config)
3135
{
3236
base.Initialize(name, config);
3337

38+
if (!Boolean.TryParse(config?[preloadTag], out _preload))
39+
_preload = true;
40+
if (!_preload && Mode == KeyValueMode.Greedy)
41+
throw new ArgumentException($"'{preloadTag}'='false' is not compatible with {KeyValueMode.Greedy} mode.");
42+
3443
_uri = config?[uriTag];
3544
_vaultName = config?[vaultNameTag];
45+
_version = config?[versionTag];
3646

3747
if (String.IsNullOrWhiteSpace(_uri))
3848
{
@@ -50,7 +60,9 @@ public override void Initialize(string name, NameValueCollection config)
5060
AzureServiceTokenProvider tokenProvider = new AzureServiceTokenProvider(_connectionString);
5161
_kvClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(tokenProvider.KeyVaultTokenCallback));
5262

53-
_allKeys = GetAllKeys();
63+
if (_preload) {
64+
_allKeys = GetAllKeys();
65+
}
5466
}
5567

5668
public override string GetValue(string key)
@@ -80,8 +92,14 @@ public override ICollection<KeyValuePair<string, string>> GetAllValues(string pr
8092

8193
private async Task<string> GetValueAsync(string key)
8294
{
83-
if (_allKeys.Contains(key, StringComparer.OrdinalIgnoreCase))
95+
if (!_preload || _allKeys.Contains(key, StringComparer.OrdinalIgnoreCase))
8496
{
97+
if (!String.IsNullOrWhiteSpace(_version))
98+
{
99+
var versionedSecret = await _kvClient.GetSecretAsync(_uri, key, _version);
100+
return versionedSecret?.Value;
101+
}
102+
85103
var secret = await _kvClient.GetSecretAsync(_uri, key);
86104
return secret?.Value;
87105
}
@@ -91,14 +109,23 @@ private async Task<string> GetValueAsync(string key)
91109

92110
private List<string> GetAllKeys()
93111
{
94-
var allSecrets = Task.Run(async () => { return await _kvClient.GetSecretsAsync(_uri); }).Result;
95-
96-
List<Task> tasks = new List<Task>();
97112
List<string> keys = new List<string>(); // KeyVault keys are case-insensitive. There won't be case-duplicates. List<> should be fine.
98113

114+
// Get first page of secret keys
115+
var allSecrets = Task.Run(async () => { return await _kvClient.GetSecretsAsync(_uri); }).Result;
99116
foreach (var secretItem in allSecrets)
100117
keys.Add(secretItem.Identifier.Name);
101118

119+
// If there more more pages, get those too
120+
string nextPage = allSecrets.NextPageLink;
121+
while (!String.IsNullOrWhiteSpace(nextPage))
122+
{
123+
var moreSecrets = Task.Run(async () => { return await _kvClient.GetSecretsNextAsync(nextPage); }).Result;
124+
foreach (var secretItem in moreSecrets)
125+
keys.Add(secretItem.Identifier.Name);
126+
nextPage = moreSecrets.NextPageLink;
127+
}
128+
102129
return keys;
103130
}
104131
}

0 commit comments

Comments
 (0)