Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ public class AzureAppConfigurationKeyVaultOptions
internal TimeSpan? DefaultSecretRefreshInterval = null;
internal bool IsKeyVaultRefreshConfigured = false;

/// <summary>
/// Flag to indicate whether Key Vault references should be resolved in parallel. Disabled by default.
/// </summary>
public bool ParallelSecretResolutionEnabled { get; set; }

/// <summary>
/// Sets the credentials used to authenticate to key vaults that have no registered <see cref="SecretClient"/>.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,12 @@ internal IEnumerable<IKeyValueAdapter> Adapters
/// <summary>
/// Flag to indicate whether Key Vault options have been configured.
/// </summary>
internal bool IsKeyVaultConfigured { get; private set; } = false;
internal bool IsKeyVaultConfigured { get; private set; }

/// <summary>
/// Flag to indicate whether Key Vault secret values will be refreshed automatically.
/// </summary>
internal bool IsKeyVaultRefreshConfigured { get; private set; } = false;
internal bool IsKeyVaultRefreshConfigured { get; private set; }

/// <summary>
/// Indicates all feature flag features used by the application.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -625,16 +625,19 @@ private async Task<Dictionary<string, string>> PrepareData(Dictionary<string, Co
_requestTracingOptions.ResetAiConfigurationTracing();
}

foreach (KeyValuePair<string, ConfigurationSetting> kvp in data)
foreach (IKeyValueAdapter adapter in _options.Adapters)
{
IEnumerable<KeyValuePair<string, string>> keyValuePairs = null;
await adapter.PreloadAsync(data.Values, _logger, cancellationToken).ConfigureAwait(false);
}

foreach (KeyValuePair<string, ConfigurationSetting> kvp in data)
{
if (_requestTracingEnabled && _requestTracingOptions != null)
{
_requestTracingOptions.UpdateAiConfigurationTracing(kvp.Value.ContentType);
}

keyValuePairs = await ProcessAdapters(kvp.Value, cancellationToken).ConfigureAwait(false);
IEnumerable<KeyValuePair<string, string>> keyValuePairs = await ProcessAdapters(kvp.Value, cancellationToken).ConfigureAwait(false);

foreach (KeyValuePair<string, string> kv in keyValuePairs)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
using Azure;
using Azure.Data.AppConfiguration;
using Azure.Security.KeyVault.Secrets;
using Microsoft.Extensions.Configuration.AzureAppConfiguration.Extensions;
using Microsoft.Extensions.Configuration.AzureAppConfiguration.FeatureManagement;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Mime;
using System.Text.Json;
using System.Threading;
Expand All @@ -18,8 +16,6 @@ namespace Microsoft.Extensions.Configuration.AzureAppConfiguration.AzureKeyVault
{
internal class AzureKeyVaultKeyValueAdapter : IKeyValueAdapter
{
private const string AzureIdentityAssemblyName = "Azure.Identity";

private readonly AzureKeyVaultSecretProvider _secretProvider;

public AzureKeyVaultKeyValueAdapter(AzureKeyVaultSecretProvider secretProvider)
Expand All @@ -37,42 +33,17 @@ public async Task<IEnumerable<KeyValuePair<string, string>>> ProcessKeyValue(Con
// Uri validation
if (string.IsNullOrEmpty(secretRefUri) || !Uri.TryCreate(secretRefUri, UriKind.Absolute, out Uri secretUri) || !KeyVaultSecretIdentifier.TryCreate(secretUri, out KeyVaultSecretIdentifier secretIdentifier))
{
throw CreateKeyVaultReferenceException("Invalid Key vault secret identifier.", setting, null, secretRefUri);
throw KeyVaultReferenceException.Create("Invalid Key vault secret identifier.", setting, null, secretRefUri);
}

string secret;

try
{
secret = await _secretProvider.GetSecretValue(secretIdentifier, setting.Key, setting.Label, logger, cancellationToken).ConfigureAwait(false);
}
catch (Exception e) when (e is UnauthorizedAccessException || (e.Source?.Equals(AzureIdentityAssemblyName, StringComparison.OrdinalIgnoreCase) ?? false))
{
throw CreateKeyVaultReferenceException(e.Message, setting, e, secretRefUri);
}
catch (Exception e) when (e is RequestFailedException || ((e as AggregateException)?.InnerExceptions?.All(e => e is RequestFailedException) ?? false))
{
throw CreateKeyVaultReferenceException("Key vault error.", setting, e, secretRefUri);
}
string secret = await _secretProvider.GetSecretValue(secretIdentifier, setting, logger, cancellationToken).ConfigureAwait(false);

return new KeyValuePair<string, string>[]
{
new KeyValuePair<string, string>(setting.Key, secret)
};
}

KeyVaultReferenceException CreateKeyVaultReferenceException(string message, ConfigurationSetting setting, Exception inner, string secretRefUri = null)
{
return new KeyVaultReferenceException(message, inner)
{
Key = setting.Key,
Label = setting.Label,
Etag = setting.ETag.ToString(),
ErrorCode = (inner as RequestFailedException)?.ErrorCode,
SecretIdentifier = secretRefUri
};
}

public bool CanProcess(ConfigurationSetting setting)
{
if (setting == null ||
Expand Down Expand Up @@ -116,6 +87,81 @@ public bool NeedsRefresh()
return _secretProvider.ShouldRefreshKeyVaultSecrets();
}

public async Task PreloadAsync(IEnumerable<ConfigurationSetting> settings, Logger logger, CancellationToken cancellationToken)
{
if (settings == null)
{
return;
}

var seen = new HashSet<Uri>();
var toFetch = new List<(KeyVaultSecretIdentifier, ConfigurationSetting)>();

foreach (ConfigurationSetting setting in settings)
{
if (!CanProcess(setting))
{
continue;
Comment thread
linglingye001 marked this conversation as resolved.
}

string secretRefUri = ParseSecretReferenceUri(setting);

if (string.IsNullOrEmpty(secretRefUri) ||
!Uri.TryCreate(secretRefUri, UriKind.Absolute, out Uri secretUri) ||
!KeyVaultSecretIdentifier.TryCreate(secretUri, out KeyVaultSecretIdentifier secretIdentifier))
{
throw KeyVaultReferenceException.Create("Invalid Key vault secret identifier.", setting, null, secretRefUri);
}

if (seen.Add(secretIdentifier.SourceId))
{
toFetch.Add((secretIdentifier, setting));
}
}

if (toFetch.Count == 0)
{
return;
}

if (_secretProvider.IsParallelSecretResolutionEnabled)
{
using (var semaphore = new SemaphoreSlim(KeyVaultConstants.MaxParallelSecretResolution))
{
async Task ResolveSecretAsync(KeyVaultSecretIdentifier identifier, ConfigurationSetting setting)
{
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);

try
{
await _secretProvider.GetSecretValue(identifier, setting, logger, cancellationToken).ConfigureAwait(false);
}
finally
{
semaphore.Release();
}
}

Task[] tasks = new Task[toFetch.Count];

for (int i = 0; i < toFetch.Count; i++)
{
(KeyVaultSecretIdentifier identifier, ConfigurationSetting setting) = toFetch[i];
tasks[i] = ResolveSecretAsync(identifier, setting);
}

await Task.WhenAll(tasks).ConfigureAwait(false);
}
}
else
{
foreach ((KeyVaultSecretIdentifier identifier, ConfigurationSetting setting) in toFetch)
{
await _secretProvider.GetSecretValue(identifier, setting, logger, cancellationToken).ConfigureAwait(false);
}
}
}

private string ParseSecretReferenceUri(ConfigurationSetting setting)
{
string secretRefUri = null;
Expand All @@ -126,7 +172,7 @@ private string ParseSecretReferenceUri(ConfigurationSetting setting)

if (reader.Read() && reader.TokenType != JsonTokenType.StartObject)
{
throw CreateKeyVaultReferenceException(ErrorMessages.InvalidKeyVaultReference, setting, null, null);
throw KeyVaultReferenceException.Create(ErrorMessages.InvalidKeyVaultReference, setting, null, null);
}

while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
Expand All @@ -144,7 +190,7 @@ private string ParseSecretReferenceUri(ConfigurationSetting setting)
}
else
{
throw CreateKeyVaultReferenceException(ErrorMessages.InvalidKeyVaultReference, setting, null, null);
throw KeyVaultReferenceException.Create(ErrorMessages.InvalidKeyVaultReference, setting, null, null);
}
}
else
Expand All @@ -155,7 +201,7 @@ private string ParseSecretReferenceUri(ConfigurationSetting setting)
}
catch (JsonException e)
{
throw CreateKeyVaultReferenceException(ErrorMessages.InvalidKeyVaultReference, setting, e, null);
throw KeyVaultReferenceException.Create(ErrorMessages.InvalidKeyVaultReference, setting, e, null);
}

return secretRefUri;
Expand Down
Loading
Loading