KeyVault HostingStartup (#114)

This commit is contained in:
Pavel Krymets 2017-10-31 10:13:53 -07:00 committed by GitHub
parent 5f164857fa
commit 9a0064285d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 272 additions and 5 deletions

View File

@ -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")]

View File

@ -5,9 +5,6 @@ using Microsoft.AspNetCore.Hosting;
[assembly: HostingStartup(typeof(Microsoft.AspNetCore.AzureAppServices.HostingStartup.AzureAppServicesHostingStartup))]
// To be able to build as <OutputType>Exe</OutputType>
internal class Program { public static void Main() { } }
namespace Microsoft.AspNetCore.AzureAppServices.HostingStartup
{
/// <summary>
@ -15,13 +12,21 @@ namespace Microsoft.AspNetCore.AzureAppServices.HostingStartup
/// </summary>
public class AzureAppServicesHostingStartup : IHostingStartup
{
private const string HostingStartupName = "AppServices";
private const string DiagnosticsFeatureName = "DiagnosticsEnabled";
/// <summary>
/// Calls UseAzureAppServices
/// </summary>
/// <param name="builder"></param>
public void Configure(IWebHostBuilder builder)
{
builder.UseAzureAppServices();
var baseConfiguration = HostingStartupConfigurationExtensions.GetBaseConfiguration();
if (baseConfiguration.IsEnabled(HostingStartupName, DiagnosticsFeatureName))
{
builder.UseAzureAppServices();
}
}
}
}

View File

@ -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
{
/// <summary>
/// A dynamic KeyVault lightup experience
/// </summary>
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";
/// <inheritdoc />
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());
}
}
}

View File

@ -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);
}
}
}

View File

@ -17,4 +17,12 @@
<ProjectReference Include="..\Microsoft.AspNetCore.AzureAppServicesIntegration\Microsoft.AspNetCore.AzureAppServicesIntegration.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
<PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.AzureKeyVault" />
<PackageReference Include="Microsoft.AspNetCore.Identity.Service.AzureKeyVault" />
<PackageReference Include="Microsoft.Azure.Services.AppAuthentication" />
</ItemGroup>
</Project>

View File

@ -5,7 +5,7 @@
<Description>This extension enables additional functionality for ASP.NET Core on Azure WebSites, such as enabling Azure logging.</Description>
<TargetFramework>net461</TargetFramework>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<PackageTags>aspnet;logging;aspnetcore;AzureSiteExtension</PackageTags>
<PackageTags>aspnet;logging;aspnetcore;AzureSiteExtension;keyvault;configuration;dataprotection</PackageTags>
<PackageType>AzureSiteExtension</PackageType>
<IncludeBuildOutput>false</IncludeBuildOutput>
<ContentTargetFolders>content</ContentTargetFolders>

View File

@ -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<IServiceCollection, KeyVaultClient, string> _dataProtectionCallback;
private readonly Action<IConfigurationBuilder, KeyVaultClient, string> _configurationCallback;
public MockAzureKeyVaultHostingStartup(
Action<IServiceCollection, KeyVaultClient, string> dataProtectionCallback,
Action<IConfigurationBuilder, KeyVaultClient, string> 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);
}
}
}
}

View File

@ -7,6 +7,7 @@
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.AzureAppServicesIntegration\Microsoft.AspNetCore.AzureAppServicesIntegration.csproj" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.AzureAppServices.HostingStartup\Microsoft.AspNetCore.AzureAppServices.HostingStartup.csproj" />
</ItemGroup>
<ItemGroup>