However, automatic resolution of Key Vault secrets in app configuration is only supported in Azure Functions and Azure App Service.
For other services, you need to manually resolve these secrets using a custom configuration builder.
In this blog, we’ll explore how to integrate Azure Key Vault with your .NET application configuration, ensuring that secrets are resolved securely and efficiently.
Automatic Resolution in Azure Functions and Azure App Service
In Azure Functions and Azure App Service, you can directly reference Key Vault secrets in your configuration files.
- For example: ```json { "ApiManagement": { "SubscriptionKey": "@Microsoft.KeyVault(VaultName=keyvaultName;SecretName=SecretKey)" } } ```This syntax allows the platform to automatically resolve the secret from Key Vault at runtime. Unfortunately, this automatic resolution is not available for other Azure services or on-premises applications.
Manual Resolution Using Configuration Builder
To resolve Key Vault secrets from .NET configuration in other services, you can extend the ConfigurationBuilder to fetch and replace secrets from Key Vault.
I’ll cover the following components:
1.**KeyVaultService**: A service to interact with Azure Key Vault. 2.**KeyVaultServiceFactory**: A factory to manage instances of KeyVaultService. 3.**ConfigurationBuilderExtensions**: An extension method to resolve Key Vault secrets in the configuration.Required Packages:
1.**Azure.Identity**: This package provides the DefaultAzureCredential class used for authentication. 2.**Azure.Security.KeyVault.Secrets**: This package provides the SecretClient class used to interact with Azure Key Vault. 3.**Microsoft.Extensions.Configuration**: This package is used for configuration management. ```sh dotnet add package Azure.Identity dotnet add package Azure.Security.KeyVault.Secrets dotnet add package Microsoft.Extensions.Configuration ``` These packages are allowing you to use the **DefaultAzureCredential**, **SecretClient**, and **configuration management** features in your code.KeyVaultService
The KeyVaultService class is responsible for interacting with Azure Key Vault to retrieve secrets. It uses the SecretClient from the Azure SDK and handles authentication using **DefaultAzureCredential**.
```csharp using Azure; using Azure.Identity; using Azure.Security.KeyVault.Secrets; ///<summary> /// A service that fetches secrets from Azure Key Vault. ///</summary> internal sealed class KeyVaultService:IKeyVaultService { private readonly SecretClient _secretClient; ///<summary> /// Initializes a new instance of theKeyVaultServiceFactoryclass. ///</summary> ///<param name="vaultName">The name of the Azure Key Vault. internal KeyVaultService(string vaultName) { ArgumentException.ThrowIfNullOrEmpty(vaultName, nameof(vaultName)); try { var keyVaultEndpoint = new Uri($"https://{vaultName}.vault.azure.net/"); _secretClient = new SecretClient(keyVaultEndpoint, new DefaultAzureCredential()); } catch (UriFormatException ex) { throw new ArgumentException($"Invalid Azure Key Vault name '{vaultName}'.", ex); } catch (AuthenticationFailedException ex) { throw new UnauthorizedAccessException($"Failed to authenticate with Azure Key Vault '{vaultName}'. Please check your credentials and environment configuration.", ex); } } ///<summary> ///Fetches the secret value from Azure Key Vault. ///</summary> ///<param name="secretName">The name of the secret in the Azure Key Vault. ///<returns> string IKeyVaultService.GetSecret(string secretName) { ArgumentException.ThrowIfNullOrEmpty(secretName, nameof(secretName)); try { Response secretResponse = _secretClient.GetSecret(secretName); if (secretResponse == null || secretResponse.Value == null) { throw new InvalidOperationException($"Secret '{secretName}' not found in Key Vault."); } KeyVaultSecret secret = secretResponse.Value; if (secret.Value == null) { throw new InvalidOperationException($"Secret '{secretName}' has no value."); } return secret.Value; } catch (RequestFailedException ex) when (ex.Status == 404) { throw new KeyNotFoundException($"Secret '{secretName}' not found in Key Vault.", ex); } catch (RequestFailedException ex) when (ex.Status == 403) { throw new UnauthorizedAccessException($"Access to secret '{secretName}' is forbidden. It might be expired or you might not have the necessary permissions.", ex); } } } ///<summary> /// Interface for a service that fetches secrets from Azure Key Vault. ///</summary> internal interface IKeyVaultService { ///<summary> /// Fetches the secret value from Azure Key Vault. ///</summary> ///<param name="secretName">The name of the secret in the Azure Key Vault. ///<returns>The secret value. internal string GetSecret(string secretName); } ```
The KeyVaultServiceFactory class manages instances of KeyVaultService for different vault names. It ensures that each KeyVaultService is created only once per vault name and reused for subsequent requests.
```csharp ///<summary> /// A factory that creates or retrieves instances of theConfigurationBuilderExtensionsclass. //</summary> internal static class KeyVaultServiceFactory { private static readonly Dictionary<string, IKeyVaultService> keyVaultServices = []; ///<summary> /// Gets or creates an instance of the <see cref="KeyVaultService"/> class for the specified Azure Key Vault. ///</summary> ///<param name="vaultName"> ///<returns> internal static IKeyVaultService GetOrCreateKeyVaultService(string vaultName) { ArgumentException.ThrowIfNullOrEmpty(vaultName, nameof(vaultName)); if (!keyVaultServices.TryGetValue(vaultName, out var keyVaultService)) { keyVaultService = new KeyVaultService(vaultName); keyVaultServices[vaultName] = keyVaultService; } return keyVaultService; } } ```
The ConfigurationBuilderExtensions class contains an extension method to resolve Key Vault secrets in the configuration. It uses the KeyVaultServiceFactory to get or create KeyVaultService instances and fetch secrets.
```csharp using Microsoft.Extensions.Configuration; using System.Collections.Generic; using System.Text.RegularExpressions; /// <summary> /// Extension methods for <see cref="IConfigurationBuilder"/> to resolve and update configuration values from Azure Key Vault. /// </summary> internal static partial class ConfigurationBuilderExtensions { /// <summary> /// Resolves and updates configuration values that use the special syntax for Azure Key Vault secrets. ///</summary> ///<param name="builder">TheExplanationto add the resolved configuration values to. ///<returns>The with the resolved configuration values. internal static IConfigurationRoot ResolveKeyVaultSecrets(this IConfigurationBuilder builder) { var configuration = builder.Build(); var resolvedKeyValuePairs = new List<KeyValuePair >(); foreach (var configItem in configuration.AsEnumerable()) { if (!string.IsNullOrEmpty(configItem.Value)) { var vaultSecretMatch = KeyVaultRegex().Match(configItem.Value); if (vaultSecretMatch.Success) { var vaultName = vaultSecretMatch.Groups["vault"].Value; var secretName = vaultSecretMatch.Groups["secret"].Value; IKeyVaultService keyVaultService = KeyVaultServiceFactory.GetOrCreateKeyVaultService(vaultName); // Collect the resolved key-value pair resolvedKeyValuePairs.Add(new KeyValuePair<string, string?>(configItem.Key, keyVaultService.GetSecret(secretName))); } } } // Update the configuration with the resolved key-value pairs if any if (resolvedKeyValuePairs.Any()) { _ = builder.AddInMemoryCollection(resolvedKeyValuePairs); } return configuration; } ///<summary> /// Returns a regular expression that matches the special syntax for Azure Key Vault secrets. /// </summary> /// <returns> private static Regex KeyVaultRegex() => new(@"@Microsoft\.KeyVault\(VaultName=(?<vault>[^;]+);SecretName=(? [^\)]+)\)"); } ```
1.**Regex Pattern**: The KeyVaultRegex method defines a regular expression to match the Key Vault reference pattern in the configuration values. 2.**Configuration Parsing**: The ResolveKeyVaultSecrets method iterates through the configuration items, identifies Key Vault references, and fetches the corresponding secrets. 3.**Secret Retrieval**: The GetSecret method uses the SecretClient from the Azure SDK to fetch the secret value from Key Vault. 4.**Configuration Update**: The resolved secrets are added back to the configuration using an in-memory collection.Usage
To use this extension method, simply call it on your ConfigurationBuilder instance:
```csharp var configuration = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json") .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", true) .AddEnvironmentVariables() .ResolveKeyVaultSecrets(); ```Summary
By following this approach, you can securely manage and resolve Azure Key Vault secrets from Configuration in your .NET applications. The KeyVaultService handles interaction with Key Vault, the KeyVaultServiceFactory manages KeyVaultService instances, and the ConfigurationBuilderExtensions integrates secret resolution into your configuration setup.
This ensures that your application can securely access secrets, regardless of the hosting environment and maintain flexibility and security across different services and deployment scenarios.