diff --git a/aspnetcore/security/key-vault-configuration.md b/aspnetcore/security/key-vault-configuration.md index b39c83346a7e..b56818c9f76f 100644 --- a/aspnetcore/security/key-vault-configuration.md +++ b/aspnetcore/security/key-vault-configuration.md @@ -5,7 +5,7 @@ description: Learn how to use the Azure Key Vault Configuration Provider to conf monikerRange: '>= aspnetcore-2.1' ms.author: riande ms.custom: mvc -ms.date: 10/27/2019 +ms.date: 11/14/2019 uid: security/key-vault-configuration --- # Azure Key Vault Configuration Provider in ASP.NET Core @@ -17,18 +17,11 @@ This document explains how to use the [Microsoft Azure Key Vault](https://azure. * Controlling access to sensitive configuration data. * Meeting the requirement for FIPS 140-2 Level 2 validated Hardware Security Modules (HSM's) when storing configuration data. -This scenario is available for apps that target ASP.NET Core 2.1 or later. - -[View or download sample code](https://github.com/aspnet/AspNetCore.Docs/tree/master/aspnetcore/security/key-vault-configuration/sample) ([how to download](xref:index#how-to-download-a-sample)) +[View or download sample code](https://github.com/aspnet/AspNetCore.Docs/tree/master/aspnetcore/security/key-vault-configuration/samples) ([how to download](xref:index#how-to-download-a-sample)) ## Packages -To use the Azure Key Vault Configuration Provider, add a package reference to the [Microsoft.Extensions.Configuration.AzureKeyVault](https://www.nuget.org/packages/Microsoft.Extensions.Configuration.AzureKeyVault/) package. - -To adopt the [Managed identities for Azure resources](/azure/active-directory/managed-identities-azure-resources/overview) scenario, add a package reference to the [Microsoft.Azure.Services.AppAuthentication](https://www.nuget.org/packages/Microsoft.Azure.Services.AppAuthentication/) package. - -> [!NOTE] -> At the time of writing, the latest stable release of `Microsoft.Azure.Services.AppAuthentication`, version `1.0.3`, provides support for [system-assigned managed identities](/azure/active-directory/managed-identities-azure-resources/overview#how-does-the-managed-identities-for-azure-resources-work). Support for *user-assigned managed identities* is available in the `1.2.0-preview2` package. This topic demonstrates the use of system-managed identities, and the provided sample app uses version `1.0.3` of the `Microsoft.Azure.Services.AppAuthentication` package. +Add a package reference to the [Microsoft.Extensions.Configuration.AzureKeyVault](https://www.nuget.org/packages/Microsoft.Extensions.Configuration.AzureKeyVault/) package. ## Sample app @@ -126,9 +119,9 @@ The sample app uses an Application ID and X.509 certificate when the `#define` s 1. Navigate to **Key vaults** in the Azure portal. 1. Select the key vault that you created in the [Secret storage in the Production environment with Azure Key Vault](#secret-storage-in-the-production-environment-with-azure-key-vault) section. 1. Select **Access policies**. -1. Select **Add new**. -1. Select **Select principal** and select the registered app by name. Select the **Select** button. +1. Select **Add Access Policy**. 1. Open **Secret permissions** and provide the app with **Get** and **List** permissions. +1. Select **Select principal** and select the registered app by name. Select the **Select** button. 1. Select **OK**. 1. Select **Save**. 1. Deploy the app. @@ -140,9 +133,19 @@ The `Certificate` sample app obtains its configuration values from `IConfigurati * `config["Section:SecretName"]` * `config.GetSection("Section")["SecretName"]` -The X.509 certificate is managed by the OS. The app calls `AddAzureKeyVault` with values supplied by the *appsettings.json* file: +The X.509 certificate is managed by the OS. The app calls with values supplied by the *appsettings.json* file: + +::: moniker range=">= aspnetcore-3.0" + +[!code-csharp[](key-vault-configuration/samples/3.x/SampleApp/Program.cs?name=snippet1&highlight=20-23)] -[!code-csharp[](key-vault-configuration/sample/Program.cs?name=snippet1&highlight=20-23)] +::: moniker-end + +::: moniker range="< aspnetcore-3.0" + +[!code-csharp[](key-vault-configuration/samples/2.x/SampleApp/Program.cs?name=snippet1&highlight=20-23)] + +::: moniker-end Example values: @@ -152,7 +155,17 @@ Example values: *appsettings.json*: -[!code-json[](key-vault-configuration/sample/appsettings.json)] +::: moniker range=">= aspnetcore-3.0" + +[!code-json[](key-vault-configuration/samples/3.x/SampleApp/appsettings.json?highlight=10-12)] + +::: moniker-end + +::: moniker range="< aspnetcore-3.0" + +[!code-json[](key-vault-configuration/samples/2.x/SampleApp/appsettings.json?highlight=10-12)] + +::: moniker-end When you run the app, a webpage shows the loaded secret values. In the Development environment, secret values load with the `_dev` suffix. In the Production environment, the values load with the `_prod` suffix. @@ -179,10 +192,20 @@ az keyvault set-policy --name '{KEY VAULT NAME}' --object-id {OBJECT ID} --secre The sample app: * Creates an instance of the `AzureServiceTokenProvider` class without a connection string. When a connection string isn't provided, the provider attempts to obtain an access token from Managed identities for Azure resources. -* A new `KeyVaultClient` is created with the `AzureServiceTokenProvider` instance token callback. -* The `KeyVaultClient` instance is used with a default implementation of `IKeyVaultSecretManager` that loads all secret values and replaces double-dashes (`--`) with colons (`:`) in key names. +* A new is created with the `AzureServiceTokenProvider` instance token callback. +* The instance is used with a default implementation of that loads all secret values and replaces double-dashes (`--`) with colons (`:`) in key names. -[!code-csharp[](key-vault-configuration/sample/Program.cs?name=snippet2&highlight=13-21)] +::: moniker range=">= aspnetcore-3.0" + +[!code-csharp[](key-vault-configuration/samples/3.x/SampleApp/Program.cs?name=snippet2&highlight=13-21)] + +::: moniker-end + +::: moniker range="< aspnetcore-3.0" + +[!code-csharp[](key-vault-configuration/samples/2.x/SampleApp/Program.cs?name=snippet2&highlight=13-21)] + +::: moniker-end Key vault name example value: `contosovault` @@ -200,22 +223,50 @@ If you receive an `Access denied` error, confirm that the app is registered with For information on using the provider with a managed identity and an Azure DevOps pipeline, see [Create an Azure Resource Manager service connection to a VM with a managed service identity](/azure/devops/pipelines/library/connect-to-azure#create-an-azure-resource-manager-service-connection-to-a-vm-with-a-managed-service-identity). +::: moniker range=">= aspnetcore-3.0" + +## Configuration options + + can accept an : + +```csharp +config.AddAzureKeyVault( + new AzureKeyVaultConfigurationOptions() + { + ... + }); +``` + +| Property | Description | +| ---------------- | ----------- | +| `Client` | to use for retrieving values. | +| `Manager` | instance used to control secret loading. | +| `ReloadInterval` | `Timespan` to wait between attempts at polling the key vault for changes. The default value is `null` (configuration isn't reloaded). | +| `Vault` | Key vault URI. | + +::: moniker-end + ## Use a key name prefix -`AddAzureKeyVault` provides an overload that accepts an implementation of `IKeyVaultSecretManager`, which allows you to control how key vault secrets are converted into configuration keys. For example, you can implement the interface to load secret values based on a prefix value you provide at app startup. This allows you, for example, to load secrets based on the version of the app. + provides an overload that accepts an implementation of , which allows you to control how key vault secrets are converted into configuration keys. For example, you can implement the interface to load secret values based on a prefix value you provide at app startup. This allows you, for example, to load secrets based on the version of the app. > [!WARNING] > Don't use prefixes on key vault secrets to place secrets for multiple apps into the same key vault or to place environmental secrets (for example, *development* versus *production* secrets) into the same vault. We recommend that different apps and development/production environments use separate key vaults to isolate app environments for the highest level of security. In the following example, a secret is established in the key vault (and using the Secret Manager tool for the Development environment) for `5000-AppSecret` (periods aren't allowed in key vault secret names). This secret represents an app secret for version 5.0.0.0 of the app. For another version of the app, 5.1.0.0, a secret is added to the key vault (and using the Secret Manager tool) for `5100-AppSecret`. Each app version loads its versioned secret value into its configuration as `AppSecret`, stripping off the version as it loads the secret. -`AddAzureKeyVault` is called with a custom `IKeyVaultSecretManager`: + is called with a custom : + +[!code-csharp[](key-vault-configuration/samples_snapshot/Program.cs)] -[!code-csharp[](key-vault-configuration/sample_snapshot/Program.cs?highlight=30-34)] +The implementation reacts to the version prefixes of secrets to load the proper secret into configuration: -The `IKeyVaultSecretManager` implementation reacts to the version prefixes of secrets to load the proper secret into configuration: +* `Load` loads a secret when its name starts with the prefix. Other secrets aren't loaded. +* `GetKey`: + * Removes the prefix from the secret name. + * Replaces two dashes in any name with the `KeyDelimiter`, which is the delimiter used in configuration (usually a colon). Azure Key Vault doesn't allow a colon in secret names. -[!code-csharp[](key-vault-configuration/sample_snapshot/Startup.cs?name=snippet1)] +[!code-csharp[](key-vault-configuration/samples_snapshot/Startup.cs)] The `Load` method is called by a provider algorithm that iterates through the vault secrets to find the ones that have the version prefix. When a version prefix is found with `Load`, the algorithm uses the `GetKey` method to return the configuration name of the secret name. It strips off the version prefix from the secret's name and returns the rest of the secret name for loading into the app's configuration name-value pairs. @@ -258,7 +309,7 @@ When this approach is implemented: 1. If the app's version is changed in the project file to `5.1.0.0` and the app is run again, the secret value returned is `5.1.0.0_secret_value_dev` in the Development environment and `5.1.0.0_secret_value_prod` in Production. > [!NOTE] -> You can also provide your own `KeyVaultClient` implementation to `AddAzureKeyVault`. A custom client permits sharing a single instance of the client across the app. +> You can also provide your own implementation to . A custom client permits sharing a single instance of the client across the app. ## Bind an array to a class @@ -312,7 +363,7 @@ Configuration.Reload(); ## Disabled and expired secrets -Disabled and expired secrets throw a `KeyVaultClientException` at runtime. To prevent the app from throwing, provide the configuration using a different configuration provider or update the disabled or expired secret. +Disabled and expired secrets throw a . To prevent the app from throwing, provide the configuration using a different configuration provider or update the disabled or expired secret. ## Troubleshoot @@ -325,6 +376,7 @@ When the app fails to load configuration using the provider, an error message is * In the key vault, the configuration data (name-value pair) is incorrectly named, missing, disabled, or expired. * The app has the wrong key vault name (`KeyVaultName`), Azure AD Application Id (`AzureADApplicationId`), or Azure AD certificate thumbprint (`AzureADCertThumbprint`). * The configuration key (name) is incorrect in the app for the value you're trying to load. +* When adding the access policy for the app to the key vault, the policy was created, but the **Save** button wasn't selected in the **Access policies** UI. ## Additional resources diff --git a/aspnetcore/security/key-vault-configuration/sample/Markup.cs b/aspnetcore/security/key-vault-configuration/sample/Markup.cs deleted file mode 100644 index 0e3658941a86..000000000000 --- a/aspnetcore/security/key-vault-configuration/sample/Markup.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace SampleApp -{ - internal static class Markup - { - internal const string Text = @" - - - - Key Vault Configuration Provider Sample - - - -

Key Vault Configuration Provider Sample

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
SecretName in Key VaultObtained from ConfigurationValue
SecretNameSecretNameConfiguration[""SecretName""]{0}
Section:SecretNameSection--SecretNameConfiguration[""Section:SecretName""]{1}
Configuration.GetSection(""Section"")[""SecretName""]{2}
-
- - "; - } -} diff --git a/aspnetcore/security/key-vault-configuration/sample/Startup.cs b/aspnetcore/security/key-vault-configuration/sample/Startup.cs deleted file mode 100644 index b79b39cc2cf4..000000000000 --- a/aspnetcore/security/key-vault-configuration/sample/Startup.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using System.Text; - -namespace SampleApp -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; set; } - - public void Configure(IApplicationBuilder app) - { - app.Run(async context => - { - var encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); - var document = string.Format(Markup.Text, Configuration["SecretName"], Configuration["Section:SecretName"], Configuration.GetSection("Section")["SecretName"]); - context.Response.ContentLength = encoding.GetByteCount(document); - context.Response.ContentType = "text/html"; - await context.Response.WriteAsync(document); - }); - } - } -} diff --git a/aspnetcore/security/key-vault-configuration/sample_snapshot/Program.cs b/aspnetcore/security/key-vault-configuration/sample_snapshot/Program.cs deleted file mode 100644 index afdc4eb33fb3..000000000000 --- a/aspnetcore/security/key-vault-configuration/sample_snapshot/Program.cs +++ /dev/null @@ -1,40 +0,0 @@ -// using System.Reflection; -// using Microsoft.Azure.KeyVault; -// using Microsoft.Azure.Services.AppAuthentication; -// using Microsoft.Extensions.Configuration; -// using Microsoft.Extensions.Configuration.AzureKeyVault; - -public static IWebHostBuilder CreateWebHostBuilder(string[] args) => - WebHost.CreateDefaultBuilder(args) - .ConfigureAppConfiguration((context, config) => - { - if (context.HostingEnvironment.IsProduction()) - { - // The appVersion obtains the app version (5.0.0.0), which - // is set in the project file and obtained from the entry - // assembly. The versionPrefix holds the version without - // dot notation for the PrefixKeyVaultSecretManager. - var appVersion = Assembly.GetEntryAssembly().GetName().Version.ToString(); - var versionPrefix = appVersion.Replace(".", string.Empty); - - var builtConfig = config.Build(); - - using (var store = new X509Store(StoreName.My, - StoreLocation.CurrentUser)) - { - store.Open(OpenFlags.ReadOnly); - var certs = store.Certificates - .Find(X509FindType.FindByThumbprint, - builtConfig["AzureADCertThumbprint"], false); - - config.AddAzureKeyVault( - $"https://{builtConfig["KeyVaultName"]}.vault.azure.net/", - builtConfig["AzureADApplicationId"], - certs.OfType().Single(), - new PrefixKeyVaultSecretManager(versionPrefix)); - - store.Close(); - } - } - }) - .UseStartup(); diff --git a/aspnetcore/security/key-vault-configuration/sample_snapshot/Startup.cs b/aspnetcore/security/key-vault-configuration/sample_snapshot/Startup.cs deleted file mode 100644 index e7831356ff5c..000000000000 --- a/aspnetcore/security/key-vault-configuration/sample_snapshot/Startup.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.Azure.KeyVault.Models; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Configuration.AzureKeyVault; -using System.Reflection; -using System.Text; - -namespace SampleApp -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; set; } - - public void Configure(IApplicationBuilder app) - { - app.Run(async context => - { - var appVersion = Assembly.GetEntryAssembly().GetName().Version.ToString(); - var versionPrefix = appVersion.Replace(".", string.Empty); - var encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); - var document = string.Format(Markup.Text, versionPrefix, Configuration["AppSecret"]); - context.Response.ContentLength = encoding.GetByteCount(document); - context.Response.ContentType = "text/html"; - await context.Response.WriteAsync(document); - }); - } - } - - #region snippet1 - public class PrefixKeyVaultSecretManager : IKeyVaultSecretManager - { - private readonly string _prefix; - - public PrefixKeyVaultSecretManager(string prefix) - { - _prefix = $"{prefix}-"; - } - - public bool Load(SecretItem secret) - { - // Load a vault secret when its secret name starts with the - // prefix. Other secrets won't be loaded. - return secret.Identifier.Name.StartsWith(_prefix); - } - - public string GetKey(SecretBundle secret) - { - // Remove the prefix from the secret name and replace two - // dashes in any name with the KeyDelimiter, which is the - // delimiter used in configuration (usually a colon). Azure - // Key Vault doesn't allow a colon in secret names. - return secret.SecretIdentifier.Name - .Substring(_prefix.Length) - .Replace("--", ConfigurationPath.KeyDelimiter); - } - } - #endregion -} diff --git a/aspnetcore/security/key-vault-configuration/sample/Program.cs b/aspnetcore/security/key-vault-configuration/samples/2.x/SampleApp/Program.cs similarity index 79% rename from aspnetcore/security/key-vault-configuration/sample/Program.cs rename to aspnetcore/security/key-vault-configuration/samples/2.x/SampleApp/Program.cs index 6a15997f79ed..29cc8de83d11 100644 --- a/aspnetcore/security/key-vault-configuration/sample/Program.cs +++ b/aspnetcore/security/key-vault-configuration/samples/2.x/SampleApp/Program.cs @@ -26,6 +26,7 @@ public static void Main(string[] args) // using System.Linq; // using System.Security.Cryptography.X509Certificates; // using Microsoft.Extensions.Configuration; + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .ConfigureAppConfiguration((context, config) => @@ -34,8 +35,7 @@ public static IWebHostBuilder CreateWebHostBuilder(string[] args) => { var builtConfig = config.Build(); - using (var store = new X509Store(StoreName.My, - StoreLocation.CurrentUser)) + using (var store = new X509Store(StoreLocation.CurrentUser)) { store.Open(OpenFlags.ReadOnly); var certs = store.Certificates @@ -69,15 +69,15 @@ public static IWebHostBuilder CreateWebHostBuilder(string[] args) => { var builtConfig = config.Build(); - var azureServiceTokenProvider = new AzureServiceTokenProvider(); - var keyVaultClient = new KeyVaultClient( - new KeyVaultClient.AuthenticationCallback( - azureServiceTokenProvider.KeyVaultTokenCallback)); + var azureServiceTokenProvider = new AzureServiceTokenProvider(); + var keyVaultClient = new KeyVaultClient( + new KeyVaultClient.AuthenticationCallback( + azureServiceTokenProvider.KeyVaultTokenCallback)); - config.AddAzureKeyVault( - $"https://{builtConfig["KeyVaultName"]}.vault.azure.net/", - keyVaultClient, - new DefaultKeyVaultSecretManager()); + config.AddAzureKeyVault( + $"https://{builtConfig["KeyVaultName"]}.vault.azure.net/", + keyVaultClient, + new DefaultKeyVaultSecretManager()); } }) .UseStartup(); diff --git a/aspnetcore/security/key-vault-configuration/sample/README.md b/aspnetcore/security/key-vault-configuration/samples/2.x/SampleApp/README.md similarity index 100% rename from aspnetcore/security/key-vault-configuration/sample/README.md rename to aspnetcore/security/key-vault-configuration/samples/2.x/SampleApp/README.md diff --git a/aspnetcore/security/key-vault-configuration/sample/SampleApp.csproj b/aspnetcore/security/key-vault-configuration/samples/2.x/SampleApp/SampleApp.csproj similarity index 83% rename from aspnetcore/security/key-vault-configuration/sample/SampleApp.csproj rename to aspnetcore/security/key-vault-configuration/samples/2.x/SampleApp/SampleApp.csproj index 4bb5025c41d3..1748a157d8c8 100644 --- a/aspnetcore/security/key-vault-configuration/sample/SampleApp.csproj +++ b/aspnetcore/security/key-vault-configuration/samples/2.x/SampleApp/SampleApp.csproj @@ -8,7 +8,6 @@ - diff --git a/aspnetcore/security/key-vault-configuration/samples/2.x/SampleApp/Startup.cs b/aspnetcore/security/key-vault-configuration/samples/2.x/SampleApp/Startup.cs new file mode 100644 index 000000000000..93e05c4a9fc3 --- /dev/null +++ b/aspnetcore/security/key-vault-configuration/samples/2.x/SampleApp/Startup.cs @@ -0,0 +1,26 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; + +namespace SampleApp +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; set; } + + public void Configure(IApplicationBuilder app) + { + app.Run(async context => + { + context.Response.ContentType = "text/plain"; + await context.Response.WriteAsync($@"SecretName (Name in Key Vault: 'SecretName'){Environment.NewLine}Obtained from Configuration with Configuration[""SecretName""]{Environment.NewLine}Value: {Configuration["SecretName"]}{Environment.NewLine}{Environment.NewLine}Section:SecretName (Name in Key Vault: 'Section--SecretName'){Environment.NewLine}Obtained from Configuration with Configuration[""Section:SecretName""]{Environment.NewLine}Value: {Configuration["Section:SecretName"]}{Environment.NewLine}{Environment.NewLine}Section:SecretName (Name in Key Vault: 'Section--SecretName'){Environment.NewLine}Obtained from Configuration with Configuration.GetSection(""Section"")[""SecretName""]{Environment.NewLine}Value: {Configuration.GetSection("Section")["SecretName"]}"); + }); + } + } +} diff --git a/aspnetcore/security/key-vault-configuration/sample/appsettings.json b/aspnetcore/security/key-vault-configuration/samples/2.x/SampleApp/appsettings.json similarity index 100% rename from aspnetcore/security/key-vault-configuration/sample/appsettings.json rename to aspnetcore/security/key-vault-configuration/samples/2.x/SampleApp/appsettings.json diff --git a/aspnetcore/security/key-vault-configuration/samples/3.x/SampleApp/Program.cs b/aspnetcore/security/key-vault-configuration/samples/3.x/SampleApp/Program.cs new file mode 100644 index 000000000000..6442ccc304bc --- /dev/null +++ b/aspnetcore/security/key-vault-configuration/samples/3.x/SampleApp/Program.cs @@ -0,0 +1,93 @@ +#define Certificate // Managed +// Change to 'Managed' to run the sample in Managed Identity configuration. +// For details, see the Azure Key Vault Configuration Provider topic: +// https://docs.microsoft.com/aspnet/core/security/key-vault-configuration + +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Azure.KeyVault; +using Microsoft.Azure.Services.AppAuthentication; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration.AzureKeyVault; +using Microsoft.Extensions.Hosting; + +namespace SampleApp +{ + public static class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + +#if Certificate + #region snippet1 + // using System.Linq; + // using System.Security.Cryptography.X509Certificates; + // using Microsoft.Extensions.Configuration; + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureAppConfiguration((context, config) => + { + if (context.HostingEnvironment.IsProduction()) + { + var builtConfig = config.Build(); + + using (var store = new X509Store(StoreLocation.CurrentUser)) + { + store.Open(OpenFlags.ReadOnly); + var certs = store.Certificates + .Find(X509FindType.FindByThumbprint, + builtConfig["AzureADCertThumbprint"], false); + + config.AddAzureKeyVault( + $"https://{builtConfig["KeyVaultName"]}.vault.azure.net/", + builtConfig["AzureADApplicationId"], + certs.OfType().Single()); + + store.Close(); + } + } + }) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + #endregion +#endif + +#if Managed + #region snippet2 + // using Microsoft.Azure.KeyVault; + // using Microsoft.Azure.Services.AppAuthentication; + // using Microsoft.Extensions.Configuration.AzureKeyVault; + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureAppConfiguration((context, config) => + { + if (context.HostingEnvironment.IsProduction()) + { + var builtConfig = config.Build(); + + var azureServiceTokenProvider = new AzureServiceTokenProvider(); + var keyVaultClient = new KeyVaultClient( + new KeyVaultClient.AuthenticationCallback( + azureServiceTokenProvider.KeyVaultTokenCallback)); + + config.AddAzureKeyVault( + $"https://{builtConfig["KeyVaultName"]}.vault.azure.net/", + keyVaultClient, + new DefaultKeyVaultSecretManager()); + } + }) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + #endregion +#endif + } +} diff --git a/aspnetcore/security/key-vault-configuration/samples/3.x/SampleApp/README.md b/aspnetcore/security/key-vault-configuration/samples/3.x/SampleApp/README.md new file mode 100644 index 000000000000..1c5d18167bca --- /dev/null +++ b/aspnetcore/security/key-vault-configuration/samples/3.x/SampleApp/README.md @@ -0,0 +1,10 @@ +# Key Vault Configuration Provider Sample App + +This sample illustrates the use of the Azure Key Vault Configuration Provider. + +The sample runs in one of two modes determined by the `#define` statement at the top of the *Program.cs* file. For instructions, see [Preprocessor directives in sample code](https://docs.microsoft.com/aspnet/core#preprocessor-directives-in-sample-code): + +* `Certificate` – Demonstrates the use of an Azure Key Vault Client ID and X.509 certificate to access secrets stored in Azure Key Vault. This version of the sample can be run from any location, deployed to Azure App Service or any host capable of serving an ASP.NET Core app. +* `Managed` – Demonstrates how to use Azure's [Managed Service Identity](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview) to authenticate the app to Azure Key Vault with Azure AD authentication without credentials in the app's code or configuration. An Azure AD Client ID and Secret aren't required for the app to authenticate with Azure Key Vault. This sample must be deployed to Azure App Service to explore the Managed Identity scearnio. + +For more information, see [Azure Key Vault Configuration Provider](https://docs.microsoft.com/aspnet/core/security/key-vault-configuration). diff --git a/aspnetcore/security/key-vault-configuration/samples/3.x/SampleApp/SampleApp.csproj b/aspnetcore/security/key-vault-configuration/samples/3.x/SampleApp/SampleApp.csproj new file mode 100644 index 000000000000..0730747e9bf7 --- /dev/null +++ b/aspnetcore/security/key-vault-configuration/samples/3.x/SampleApp/SampleApp.csproj @@ -0,0 +1,13 @@ + + + + netcoreapp3.0 + + 06466f76-4a2b-4d64-94d9-72d76e179948 + + + + + + + diff --git a/aspnetcore/security/key-vault-configuration/samples/3.x/SampleApp/Startup.cs b/aspnetcore/security/key-vault-configuration/samples/3.x/SampleApp/Startup.cs new file mode 100644 index 000000000000..f90d50370d64 --- /dev/null +++ b/aspnetcore/security/key-vault-configuration/samples/3.x/SampleApp/Startup.cs @@ -0,0 +1,28 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; + +namespace SampleApp +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; set; } + + public void Configure(IApplicationBuilder app) + { + app.Run(async context => + { + context.Response.ContentType = "text/plain"; + await context.Response.WriteAsync($@"SecretName (Name in Key Vault: 'SecretName'){Environment.NewLine}Obtained from Configuration with Configuration[""SecretName""]{Environment.NewLine}Value: {Configuration["SecretName"]}{Environment.NewLine}{Environment.NewLine}Section:SecretName (Name in Key Vault: 'Section--SecretName'){Environment.NewLine}Obtained from Configuration with Configuration[""Section:SecretName""]{Environment.NewLine}Value: {Configuration["Section:SecretName"]}{Environment.NewLine}{Environment.NewLine}Section:SecretName (Name in Key Vault: 'Section--SecretName'){Environment.NewLine}Obtained from Configuration with Configuration.GetSection(""Section"")[""SecretName""]{Environment.NewLine}Value: {Configuration.GetSection("Section")["SecretName"]}"); + }); + } + } +} diff --git a/aspnetcore/security/key-vault-configuration/samples/3.x/SampleApp/appsettings.json b/aspnetcore/security/key-vault-configuration/samples/3.x/SampleApp/appsettings.json new file mode 100644 index 000000000000..ff2e451ee658 --- /dev/null +++ b/aspnetcore/security/key-vault-configuration/samples/3.x/SampleApp/appsettings.json @@ -0,0 +1,5 @@ +{ + "KeyVaultName": "Key Vault Name", + "AzureADApplicationId": "Azure AD Application ID", + "AzureADCertThumbprint": "Azure AD Certificate Thumbprint" +} diff --git a/aspnetcore/security/key-vault-configuration/samples_snapshot/Program.cs b/aspnetcore/security/key-vault-configuration/samples_snapshot/Program.cs new file mode 100644 index 000000000000..fb90fbad064b --- /dev/null +++ b/aspnetcore/security/key-vault-configuration/samples_snapshot/Program.cs @@ -0,0 +1,5 @@ +config.AddAzureKeyVault( + $"https://{builtConfig["KeyVaultName"]}.vault.azure.net/", + builtConfig["AzureADApplicationId"], + certs.OfType().Single(), + new PrefixKeyVaultSecretManager(versionPrefix)); diff --git a/aspnetcore/security/key-vault-configuration/samples_snapshot/Startup.cs b/aspnetcore/security/key-vault-configuration/samples_snapshot/Startup.cs new file mode 100644 index 000000000000..4c221af1664d --- /dev/null +++ b/aspnetcore/security/key-vault-configuration/samples_snapshot/Startup.cs @@ -0,0 +1,21 @@ +public class PrefixKeyVaultSecretManager : IKeyVaultSecretManager +{ + private readonly string _prefix; + + public PrefixKeyVaultSecretManager(string prefix) + { + _prefix = $"{prefix}-"; + } + + public bool Load(SecretItem secret) + { + return secret.Identifier.Name.StartsWith(_prefix); + } + + public string GetKey(SecretBundle secret) + { + return secret.SecretIdentifier.Name + .Substring(_prefix.Length) + .Replace("--", ConfigurationPath.KeyDelimiter); + } +}