Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 0 additions & 63 deletions .github/workflows/ci.yml

This file was deleted.

67 changes: 67 additions & 0 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
trigger:
- main
- preview
- release/*

pr:
- main
- preview
- release/*

pool:
vmImage: windows-latest

steps:
- task: PowerShell@2
displayName: 'Install .NET'
inputs:
filePath: build/install-dotnet.ps1

- task: PowerShell@2
displayName: 'Restore'
inputs:
filePath: build.ps1
arguments: -RestoreOnly

- task: PowerShell@2
displayName: 'Dotnet Build'
inputs:
filePath: build.ps1

- task: PowerShell@2
displayName: 'Dotnet Pack'
inputs:
filePath: pack.ps1

- task: AzureCLI@2
displayName: 'Dotnet Test'
inputs:
# ARM service connection using Workload Identity Federation (no client secret).
azureSubscription: '.NET Provider GitHub PR Integration Test'
addSpnToEnvironment: true
scriptType: ps
scriptLocation: inlineScript
inlineScript: |
# Configure the env vars that DefaultAzureCredential -> WorkloadIdentityCredential reads.
# Do NOT set AZURE_CLIENT_SECRET.
$env:AZURE_CLIENT_ID = $env:servicePrincipalId
$env:AZURE_TENANT_ID = $env:tenantId

$tokenPath = Join-Path "$(Agent.TempDirectory)" "azure-federated-token.txt"
Set-Content -Path $tokenPath -Value $env:idToken -NoNewline
$env:AZURE_FEDERATED_TOKEN_FILE = $tokenPath

$env:AZURE_SUBSCRIPTION_ID = (az account show --query id -o tsv)

& "$(Build.SourcesDirectory)\test.ps1"
workingDirectory: '$(Build.SourcesDirectory)'

- task: PublishTestResults@2
displayName: 'Publish Test Results'
condition: succeededOrFailed()
inputs:
testResultsFormat: 'vstest'
testResultsFiles: '**/*.trx'
searchFolder: '$(Build.SourcesDirectory)'
failTaskOnFailedTests: true
testRunTitle: 'Unit Tests'
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.15.0" />
<PackageReference Include="Azure.Identity" Version="1.21.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
</ItemGroup>
Expand Down
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 @@ -138,12 +138,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 @@ -627,16 +627,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;
}

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