Skip to content

Commit 9a00642

Browse files
authored
KeyVault HostingStartup (#114)
1 parent 5f16485 commit 9a00642

File tree

8 files changed

+272
-5
lines changed

8 files changed

+272
-5
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Runtime.CompilerServices;
5+
6+
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.AzureAppServicesIntegration.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

src/Microsoft.AspNetCore.AzureAppServices.HostingStartup/AzureStartupLoader.cs renamed to src/Microsoft.AspNetCore.AzureAppServices.HostingStartup/AzureAppServicesHostingStartup.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,28 @@
55

66
[assembly: HostingStartup(typeof(Microsoft.AspNetCore.AzureAppServices.HostingStartup.AzureAppServicesHostingStartup))]
77

8-
// To be able to build as <OutputType>Exe</OutputType>
9-
internal class Program { public static void Main() { } }
10-
118
namespace Microsoft.AspNetCore.AzureAppServices.HostingStartup
129
{
1310
/// <summary>
1411
/// A dynamic azure lightup experiance
1512
/// </summary>
1613
public class AzureAppServicesHostingStartup : IHostingStartup
1714
{
15+
private const string HostingStartupName = "AppServices";
16+
private const string DiagnosticsFeatureName = "DiagnosticsEnabled";
17+
1818
/// <summary>
1919
/// Calls UseAzureAppServices
2020
/// </summary>
2121
/// <param name="builder"></param>
2222
public void Configure(IWebHostBuilder builder)
2323
{
24-
builder.UseAzureAppServices();
24+
var baseConfiguration = HostingStartupConfigurationExtensions.GetBaseConfiguration();
25+
26+
if (baseConfiguration.IsEnabled(HostingStartupName, DiagnosticsFeatureName))
27+
{
28+
builder.UseAzureAppServices();
29+
}
2530
}
2631
}
2732
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using Microsoft.AspNetCore.DataProtection;
5+
using Microsoft.AspNetCore.Hosting;
6+
using Microsoft.Azure.KeyVault;
7+
using Microsoft.Azure.Services.AppAuthentication;
8+
using Microsoft.Extensions.Configuration;
9+
using Microsoft.Extensions.Configuration.AzureKeyVault;
10+
using Microsoft.Extensions.DependencyInjection;
11+
12+
[assembly: HostingStartup(typeof(Microsoft.AspNetCore.AzureKeyVault.HostingStartup.AzureKeyVaultHostingStartup))]
13+
14+
namespace Microsoft.AspNetCore.AzureKeyVault.HostingStartup
15+
{
16+
/// <summary>
17+
/// A dynamic KeyVault lightup experience
18+
/// </summary>
19+
public class AzureKeyVaultHostingStartup : IHostingStartup
20+
{
21+
private const string HostingStartupName = "KeyVault";
22+
private const string ConfigurationFeatureName = "ConfigurationEnabled";
23+
private const string ConfigurationVaultName = "ConfigurationVault";
24+
private const string DataProtectionFeatureName = "DataProtectionEnabled";
25+
private const string DataProtectionKeyName = "DataProtectionKey";
26+
27+
/// <inheritdoc />
28+
public void Configure(IWebHostBuilder builder)
29+
{
30+
var azureServiceTokenProvider = new AzureServiceTokenProvider();
31+
var authenticationCallback = new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback);
32+
var keyVaultClient = new KeyVaultClient(authenticationCallback);
33+
34+
var baseConfiguration = HostingStartupConfigurationExtensions.GetBaseConfiguration();
35+
36+
builder.ConfigureServices((context, collection) =>
37+
{
38+
var configuration = new ConfigurationBuilder()
39+
.AddConfiguration(baseConfiguration)
40+
.AddConfiguration(context.Configuration)
41+
.Build();
42+
43+
if (configuration.IsEnabled(HostingStartupName, DataProtectionFeatureName) &&
44+
configuration.TryGetOption(HostingStartupName, DataProtectionKeyName, out var protectionKey))
45+
{
46+
AddDataProtection(collection, keyVaultClient, protectionKey);
47+
}
48+
});
49+
50+
if (baseConfiguration.IsEnabled(HostingStartupName, ConfigurationFeatureName) &&
51+
baseConfiguration.TryGetOption(HostingStartupName, ConfigurationVaultName, out var vault))
52+
{
53+
builder.ConfigureAppConfiguration((context, configurationBuilder) =>
54+
{
55+
AddConfiguration(configurationBuilder, keyVaultClient, vault);
56+
});
57+
}
58+
}
59+
60+
internal virtual void AddDataProtection(IServiceCollection serviceCollection, KeyVaultClient client, string protectionKey)
61+
{
62+
serviceCollection.AddDataProtection().ProtectKeysWithAzureKeyVault(client, protectionKey);
63+
}
64+
65+
internal virtual void AddConfiguration(IConfigurationBuilder configurationBuilder, KeyVaultClient client, string keyVault)
66+
{
67+
configurationBuilder.AddAzureKeyVault(keyVault, client, new DefaultKeyVaultSecretManager());
68+
}
69+
}
70+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using Microsoft.Extensions.Configuration;
5+
6+
namespace Microsoft.AspNetCore.Hosting
7+
{
8+
internal static class HostingStartupConfigurationExtensions
9+
{
10+
public static IConfiguration GetBaseConfiguration()
11+
{
12+
return new ConfigurationBuilder()
13+
.AddEnvironmentVariables(prefix: "ASPNETCORE_")
14+
.Build();
15+
}
16+
public static bool IsEnabled(this IConfiguration configuration, string hostingStartupName, string featureName)
17+
{
18+
if (configuration.TryGetOption(hostingStartupName, featureName, out var value))
19+
{
20+
value = value.ToLowerInvariant();
21+
return value != "false" && value != "0";
22+
}
23+
24+
return true;
25+
}
26+
27+
public static bool TryGetOption(this IConfiguration configuration, string hostingStartupName, string featureName, out string value)
28+
{
29+
value = configuration[$"HostingStartup:{hostingStartupName}:{featureName}"];
30+
return !string.IsNullOrEmpty(value);
31+
}
32+
}
33+
}

src/Microsoft.AspNetCore.AzureAppServices.HostingStartup/Microsoft.AspNetCore.AzureAppServices.HostingStartup.csproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,12 @@
1717
<ProjectReference Include="..\Microsoft.AspNetCore.AzureAppServicesIntegration\Microsoft.AspNetCore.AzureAppServicesIntegration.csproj" />
1818
</ItemGroup>
1919

20+
<ItemGroup>
21+
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
22+
<PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" />
23+
<PackageReference Include="Microsoft.AspNetCore.DataProtection.AzureKeyVault" />
24+
<PackageReference Include="Microsoft.AspNetCore.Identity.Service.AzureKeyVault" />
25+
<PackageReference Include="Microsoft.Azure.Services.AppAuthentication" />
26+
</ItemGroup>
27+
2028
</Project>

src/Microsoft.AspNetCore.AzureAppServices.SiteExtension/Microsoft.AspNetCore.AzureAppServices.SiteExtension.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<Description>This extension enables additional functionality for ASP.NET Core on Azure WebSites, such as enabling Azure logging.</Description>
66
<TargetFramework>net461</TargetFramework>
77
<GenerateDocumentationFile>false</GenerateDocumentationFile>
8-
<PackageTags>aspnet;logging;aspnetcore;AzureSiteExtension</PackageTags>
8+
<PackageTags>aspnet;logging;aspnetcore;AzureSiteExtension;keyvault;configuration;dataprotection</PackageTags>
99
<PackageType>AzureSiteExtension</PackageType>
1010
<IncludeBuildOutput>false</IncludeBuildOutput>
1111
<ContentTargetFolders>content</ContentTargetFolders>
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Microsoft.AspNetCore.Builder;
6+
using Microsoft.AspNetCore.Hosting;
7+
using Microsoft.AspNetCore.TestHost;
8+
using Microsoft.Azure.KeyVault;
9+
using Microsoft.Extensions.Configuration;
10+
using Microsoft.Extensions.DependencyInjection;
11+
using Xunit;
12+
13+
namespace Microsoft.AspNetCore.AzureKeyVault.HostingStartup.Tests
14+
{
15+
public class HostinStartupTests
16+
{
17+
[Fact]
18+
public void Configure_AddsDataProtection()
19+
{
20+
Environment.SetEnvironmentVariable("ASPNETCORE_HostingStartup__KeyVault__DataProtectionEnabled", null);
21+
Environment.SetEnvironmentVariable("ASPNETCORE_HostingStartup__KeyVault__DataProtectionKey", "http://vault");
22+
23+
var callbackCalled = false;
24+
var builder = new WebHostBuilder().Configure(app => { });
25+
var mockHostingStartup = new MockAzureKeyVaultHostingStartup(
26+
(collection, client, key) =>
27+
{
28+
callbackCalled = true;
29+
Assert.NotNull(collection);
30+
Assert.NotNull(client);
31+
Assert.Equal("http://vault", key);
32+
},
33+
(configurationBuilder, client, vault) => {}
34+
);
35+
36+
mockHostingStartup.Configure(builder);
37+
var _ = new TestServer(builder);
38+
39+
Assert.True(callbackCalled);
40+
}
41+
42+
[Theory]
43+
[InlineData("0")]
44+
[InlineData("FALSE")]
45+
[InlineData("false")]
46+
public void Configure_SkipsAddsDataProtection_IfDisabled(string value)
47+
{
48+
Environment.SetEnvironmentVariable("ASPNETCORE_HostingStartup__KeyVault__DataProtectionEnabled", value);
49+
Environment.SetEnvironmentVariable("ASPNETCORE_HostingStartup__KeyVault__DataProtectionKey", "http://vault");
50+
51+
var callbackCalled = false;
52+
var builder = new WebHostBuilder().Configure(app => { });
53+
var mockHostingStartup = new MockAzureKeyVaultHostingStartup(
54+
(collection, client, key) =>
55+
{
56+
callbackCalled = true;
57+
},
58+
(configurationBuilder, client, vault) => {}
59+
);
60+
61+
mockHostingStartup.Configure(builder);
62+
var _ = new TestServer(builder);
63+
64+
Assert.False(callbackCalled);
65+
}
66+
67+
[Fact]
68+
public void Configure_AddsConfiguration()
69+
{
70+
Environment.SetEnvironmentVariable("ASPNETCORE_HostingStartup__KeyVault__ConfigurationEnabled", null);
71+
Environment.SetEnvironmentVariable("ASPNETCORE_HostingStartup__KeyVault__ConfigurationVault", "http://vault");
72+
73+
var callbackCalled = false;
74+
var builder = new WebHostBuilder().Configure(app => { });
75+
76+
var mockHostingStartup = new MockAzureKeyVaultHostingStartup(
77+
(collection, client, key) => { },
78+
(configurationBuilder, client, vault) =>
79+
{
80+
callbackCalled = true;
81+
Assert.NotNull(configurationBuilder);
82+
Assert.NotNull(client);
83+
Assert.Equal("http://vault", vault);
84+
}
85+
);
86+
87+
mockHostingStartup.Configure(builder);
88+
var _ = new TestServer(builder);
89+
90+
Assert.True(callbackCalled);
91+
}
92+
93+
[Theory]
94+
[InlineData("0")]
95+
[InlineData("FALSE")]
96+
[InlineData("false")]
97+
public void Configure_SkipsConfiguration_IfDisabled(string value)
98+
{
99+
Environment.SetEnvironmentVariable("ASPNETCORE_HostingStartup__KeyVault__ConfigurationEnabled", value);
100+
Environment.SetEnvironmentVariable("ASPNETCORE_HostingStartup__KeyVault__ConfigurationVault", "http://vault");
101+
102+
var callbackCalled = false;
103+
var builder = new WebHostBuilder().Configure(app => { });
104+
105+
var mockHostingStartup = new MockAzureKeyVaultHostingStartup(
106+
(collection, client, key) => { },
107+
(configurationBuilder, client, vault) =>
108+
{
109+
callbackCalled = true;
110+
}
111+
);
112+
113+
mockHostingStartup.Configure(builder);
114+
var _ = new TestServer(builder);
115+
116+
Assert.False(callbackCalled);
117+
}
118+
119+
private class MockAzureKeyVaultHostingStartup : AzureKeyVaultHostingStartup
120+
{
121+
private readonly Action<IServiceCollection, KeyVaultClient, string> _dataProtectionCallback;
122+
123+
private readonly Action<IConfigurationBuilder, KeyVaultClient, string> _configurationCallback;
124+
125+
public MockAzureKeyVaultHostingStartup(
126+
Action<IServiceCollection, KeyVaultClient, string> dataProtectionCallback,
127+
Action<IConfigurationBuilder, KeyVaultClient, string> configurationCallback)
128+
{
129+
_dataProtectionCallback = dataProtectionCallback;
130+
_configurationCallback = configurationCallback;
131+
}
132+
133+
internal override void AddDataProtection(IServiceCollection serviceCollection, KeyVaultClient client, string protectionKey)
134+
{
135+
_dataProtectionCallback(serviceCollection, client, protectionKey);
136+
}
137+
138+
internal override void AddConfiguration(IConfigurationBuilder configurationBuilder, KeyVaultClient client, string keyVault)
139+
{
140+
_configurationCallback(configurationBuilder, client, keyVault);
141+
}
142+
}
143+
}
144+
}

test/Microsoft.AspNetCore.AzureAppServicesIntegration.Tests/Microsoft.AspNetCore.AzureAppServicesIntegration.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
<ItemGroup>
99
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.AzureAppServicesIntegration\Microsoft.AspNetCore.AzureAppServicesIntegration.csproj" />
10+
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.AzureAppServices.HostingStartup\Microsoft.AspNetCore.AzureAppServices.HostingStartup.csproj" />
1011
</ItemGroup>
1112

1213
<ItemGroup>

0 commit comments

Comments
 (0)