diff --git a/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup/AssemblyInfo.cs b/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup/AssemblyInfo.cs
new file mode 100644
index 0000000000..6852ee94a9
--- /dev/null
+++ b/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup/AssemblyInfo.cs
@@ -0,0 +1,6 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.AzureAppServicesIntegration.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup/AzureStartupLoader.cs b/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup/AzureAppServicesHostingStartup.cs
similarity index 65%
rename from src/Microsoft.AspNetCore.AzureAppServices.HostingStartup/AzureStartupLoader.cs
rename to src/Microsoft.AspNetCore.AzureAppServices.HostingStartup/AzureAppServicesHostingStartup.cs
index 6bc3616701..5987e7cb58 100644
--- a/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup/AzureStartupLoader.cs
+++ b/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup/AzureAppServicesHostingStartup.cs
@@ -5,9 +5,6 @@ using Microsoft.AspNetCore.Hosting;
[assembly: HostingStartup(typeof(Microsoft.AspNetCore.AzureAppServices.HostingStartup.AzureAppServicesHostingStartup))]
-// To be able to build as Exe
-internal class Program { public static void Main() { } }
-
namespace Microsoft.AspNetCore.AzureAppServices.HostingStartup
{
///
@@ -15,13 +12,21 @@ namespace Microsoft.AspNetCore.AzureAppServices.HostingStartup
///
public class AzureAppServicesHostingStartup : IHostingStartup
{
+ private const string HostingStartupName = "AppServices";
+ private const string DiagnosticsFeatureName = "DiagnosticsEnabled";
+
///
/// Calls UseAzureAppServices
///
///
public void Configure(IWebHostBuilder builder)
{
- builder.UseAzureAppServices();
+ var baseConfiguration = HostingStartupConfigurationExtensions.GetBaseConfiguration();
+
+ if (baseConfiguration.IsEnabled(HostingStartupName, DiagnosticsFeatureName))
+ {
+ builder.UseAzureAppServices();
+ }
}
}
}
diff --git a/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup/AzureKeyVaultHostingStartup.cs b/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup/AzureKeyVaultHostingStartup.cs
new file mode 100644
index 0000000000..e9c2766669
--- /dev/null
+++ b/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup/AzureKeyVaultHostingStartup.cs
@@ -0,0 +1,70 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.DataProtection;
+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.DependencyInjection;
+
+[assembly: HostingStartup(typeof(Microsoft.AspNetCore.AzureKeyVault.HostingStartup.AzureKeyVaultHostingStartup))]
+
+namespace Microsoft.AspNetCore.AzureKeyVault.HostingStartup
+{
+ ///
+ /// A dynamic KeyVault lightup experience
+ ///
+ public class AzureKeyVaultHostingStartup : IHostingStartup
+ {
+ private const string HostingStartupName = "KeyVault";
+ private const string ConfigurationFeatureName = "ConfigurationEnabled";
+ private const string ConfigurationVaultName = "ConfigurationVault";
+ private const string DataProtectionFeatureName = "DataProtectionEnabled";
+ private const string DataProtectionKeyName = "DataProtectionKey";
+
+ ///
+ public void Configure(IWebHostBuilder builder)
+ {
+ var azureServiceTokenProvider = new AzureServiceTokenProvider();
+ var authenticationCallback = new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback);
+ var keyVaultClient = new KeyVaultClient(authenticationCallback);
+
+ var baseConfiguration = HostingStartupConfigurationExtensions.GetBaseConfiguration();
+
+ builder.ConfigureServices((context, collection) =>
+ {
+ var configuration = new ConfigurationBuilder()
+ .AddConfiguration(baseConfiguration)
+ .AddConfiguration(context.Configuration)
+ .Build();
+
+ if (configuration.IsEnabled(HostingStartupName, DataProtectionFeatureName) &&
+ configuration.TryGetOption(HostingStartupName, DataProtectionKeyName, out var protectionKey))
+ {
+ AddDataProtection(collection, keyVaultClient, protectionKey);
+ }
+ });
+
+ if (baseConfiguration.IsEnabled(HostingStartupName, ConfigurationFeatureName) &&
+ baseConfiguration.TryGetOption(HostingStartupName, ConfigurationVaultName, out var vault))
+ {
+ builder.ConfigureAppConfiguration((context, configurationBuilder) =>
+ {
+ AddConfiguration(configurationBuilder, keyVaultClient, vault);
+ });
+ }
+ }
+
+ internal virtual void AddDataProtection(IServiceCollection serviceCollection, KeyVaultClient client, string protectionKey)
+ {
+ serviceCollection.AddDataProtection().ProtectKeysWithAzureKeyVault(client, protectionKey);
+ }
+
+ internal virtual void AddConfiguration(IConfigurationBuilder configurationBuilder, KeyVaultClient client, string keyVault)
+ {
+ configurationBuilder.AddAzureKeyVault(keyVault, client, new DefaultKeyVaultSecretManager());
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup/HostingStartupConfigurationExtensions.cs b/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup/HostingStartupConfigurationExtensions.cs
new file mode 100644
index 0000000000..55ffdc6710
--- /dev/null
+++ b/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup/HostingStartupConfigurationExtensions.cs
@@ -0,0 +1,33 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.Extensions.Configuration;
+
+namespace Microsoft.AspNetCore.Hosting
+{
+ internal static class HostingStartupConfigurationExtensions
+ {
+ public static IConfiguration GetBaseConfiguration()
+ {
+ return new ConfigurationBuilder()
+ .AddEnvironmentVariables(prefix: "ASPNETCORE_")
+ .Build();
+ }
+ public static bool IsEnabled(this IConfiguration configuration, string hostingStartupName, string featureName)
+ {
+ if (configuration.TryGetOption(hostingStartupName, featureName, out var value))
+ {
+ value = value.ToLowerInvariant();
+ return value != "false" && value != "0";
+ }
+
+ return true;
+ }
+
+ public static bool TryGetOption(this IConfiguration configuration, string hostingStartupName, string featureName, out string value)
+ {
+ value = configuration[$"HostingStartup:{hostingStartupName}:{featureName}"];
+ return !string.IsNullOrEmpty(value);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup/Microsoft.AspNetCore.AzureAppServices.HostingStartup.csproj b/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup/Microsoft.AspNetCore.AzureAppServices.HostingStartup.csproj
index 140db603d0..6b2d556083 100644
--- a/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup/Microsoft.AspNetCore.AzureAppServices.HostingStartup.csproj
+++ b/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup/Microsoft.AspNetCore.AzureAppServices.HostingStartup.csproj
@@ -17,4 +17,12 @@
+
+
+
+
+
+
+
+
diff --git a/src/Microsoft.AspNetCore.AzureAppServices.SiteExtension/Microsoft.AspNetCore.AzureAppServices.SiteExtension.csproj b/src/Microsoft.AspNetCore.AzureAppServices.SiteExtension/Microsoft.AspNetCore.AzureAppServices.SiteExtension.csproj
index c286c14893..71fc124024 100644
--- a/src/Microsoft.AspNetCore.AzureAppServices.SiteExtension/Microsoft.AspNetCore.AzureAppServices.SiteExtension.csproj
+++ b/src/Microsoft.AspNetCore.AzureAppServices.SiteExtension/Microsoft.AspNetCore.AzureAppServices.SiteExtension.csproj
@@ -5,7 +5,7 @@
This extension enables additional functionality for ASP.NET Core on Azure WebSites, such as enabling Azure logging.
net461
false
- aspnet;logging;aspnetcore;AzureSiteExtension
+ aspnet;logging;aspnetcore;AzureSiteExtension;keyvault;configuration;dataprotection
AzureSiteExtension
false
content
diff --git a/test/Microsoft.AspNetCore.AzureAppServicesIntegration.Tests/HostingStartupTests.cs b/test/Microsoft.AspNetCore.AzureAppServicesIntegration.Tests/HostingStartupTests.cs
new file mode 100644
index 0000000000..62bd412f41
--- /dev/null
+++ b/test/Microsoft.AspNetCore.AzureAppServicesIntegration.Tests/HostingStartupTests.cs
@@ -0,0 +1,144 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.TestHost;
+using Microsoft.Azure.KeyVault;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit;
+
+namespace Microsoft.AspNetCore.AzureKeyVault.HostingStartup.Tests
+{
+ public class HostinStartupTests
+ {
+ [Fact]
+ public void Configure_AddsDataProtection()
+ {
+ Environment.SetEnvironmentVariable("ASPNETCORE_HostingStartup__KeyVault__DataProtectionEnabled", null);
+ Environment.SetEnvironmentVariable("ASPNETCORE_HostingStartup__KeyVault__DataProtectionKey", "http://vault");
+
+ var callbackCalled = false;
+ var builder = new WebHostBuilder().Configure(app => { });
+ var mockHostingStartup = new MockAzureKeyVaultHostingStartup(
+ (collection, client, key) =>
+ {
+ callbackCalled = true;
+ Assert.NotNull(collection);
+ Assert.NotNull(client);
+ Assert.Equal("http://vault", key);
+ },
+ (configurationBuilder, client, vault) => {}
+ );
+
+ mockHostingStartup.Configure(builder);
+ var _ = new TestServer(builder);
+
+ Assert.True(callbackCalled);
+ }
+
+ [Theory]
+ [InlineData("0")]
+ [InlineData("FALSE")]
+ [InlineData("false")]
+ public void Configure_SkipsAddsDataProtection_IfDisabled(string value)
+ {
+ Environment.SetEnvironmentVariable("ASPNETCORE_HostingStartup__KeyVault__DataProtectionEnabled", value);
+ Environment.SetEnvironmentVariable("ASPNETCORE_HostingStartup__KeyVault__DataProtectionKey", "http://vault");
+
+ var callbackCalled = false;
+ var builder = new WebHostBuilder().Configure(app => { });
+ var mockHostingStartup = new MockAzureKeyVaultHostingStartup(
+ (collection, client, key) =>
+ {
+ callbackCalled = true;
+ },
+ (configurationBuilder, client, vault) => {}
+ );
+
+ mockHostingStartup.Configure(builder);
+ var _ = new TestServer(builder);
+
+ Assert.False(callbackCalled);
+ }
+
+ [Fact]
+ public void Configure_AddsConfiguration()
+ {
+ Environment.SetEnvironmentVariable("ASPNETCORE_HostingStartup__KeyVault__ConfigurationEnabled", null);
+ Environment.SetEnvironmentVariable("ASPNETCORE_HostingStartup__KeyVault__ConfigurationVault", "http://vault");
+
+ var callbackCalled = false;
+ var builder = new WebHostBuilder().Configure(app => { });
+
+ var mockHostingStartup = new MockAzureKeyVaultHostingStartup(
+ (collection, client, key) => { },
+ (configurationBuilder, client, vault) =>
+ {
+ callbackCalled = true;
+ Assert.NotNull(configurationBuilder);
+ Assert.NotNull(client);
+ Assert.Equal("http://vault", vault);
+ }
+ );
+
+ mockHostingStartup.Configure(builder);
+ var _ = new TestServer(builder);
+
+ Assert.True(callbackCalled);
+ }
+
+ [Theory]
+ [InlineData("0")]
+ [InlineData("FALSE")]
+ [InlineData("false")]
+ public void Configure_SkipsConfiguration_IfDisabled(string value)
+ {
+ Environment.SetEnvironmentVariable("ASPNETCORE_HostingStartup__KeyVault__ConfigurationEnabled", value);
+ Environment.SetEnvironmentVariable("ASPNETCORE_HostingStartup__KeyVault__ConfigurationVault", "http://vault");
+
+ var callbackCalled = false;
+ var builder = new WebHostBuilder().Configure(app => { });
+
+ var mockHostingStartup = new MockAzureKeyVaultHostingStartup(
+ (collection, client, key) => { },
+ (configurationBuilder, client, vault) =>
+ {
+ callbackCalled = true;
+ }
+ );
+
+ mockHostingStartup.Configure(builder);
+ var _ = new TestServer(builder);
+
+ Assert.False(callbackCalled);
+ }
+
+ private class MockAzureKeyVaultHostingStartup : AzureKeyVaultHostingStartup
+ {
+ private readonly Action _dataProtectionCallback;
+
+ private readonly Action _configurationCallback;
+
+ public MockAzureKeyVaultHostingStartup(
+ Action dataProtectionCallback,
+ Action configurationCallback)
+ {
+ _dataProtectionCallback = dataProtectionCallback;
+ _configurationCallback = configurationCallback;
+ }
+
+ internal override void AddDataProtection(IServiceCollection serviceCollection, KeyVaultClient client, string protectionKey)
+ {
+ _dataProtectionCallback(serviceCollection, client, protectionKey);
+ }
+
+ internal override void AddConfiguration(IConfigurationBuilder configurationBuilder, KeyVaultClient client, string keyVault)
+ {
+ _configurationCallback(configurationBuilder, client, keyVault);
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.AzureAppServicesIntegration.Tests/Microsoft.AspNetCore.AzureAppServicesIntegration.Tests.csproj b/test/Microsoft.AspNetCore.AzureAppServicesIntegration.Tests/Microsoft.AspNetCore.AzureAppServicesIntegration.Tests.csproj
index 6e199d7fe2..8168b6f5a7 100644
--- a/test/Microsoft.AspNetCore.AzureAppServicesIntegration.Tests/Microsoft.AspNetCore.AzureAppServicesIntegration.Tests.csproj
+++ b/test/Microsoft.AspNetCore.AzureAppServicesIntegration.Tests/Microsoft.AspNetCore.AzureAppServicesIntegration.Tests.csproj
@@ -7,6 +7,7 @@
+