Skip to content
This repository was archived by the owner on Nov 21, 2018. It is now read-only.

Commit 0dacf19

Browse files
author
Cesar Blum Silveira
committed
Bind Kestrel options to config by default (#30).
1 parent d9661ed commit 0dacf19

File tree

10 files changed

+372
-2
lines changed

10 files changed

+372
-2
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<Import Project="..\..\build\dependencies.props" />
4+
5+
<PropertyGroup>
6+
<TargetFramework>netcoreapp2.0</TargetFramework>
7+
<UserSecretsId>aspnetcore-MetaPackagesSampleApp-20170421155031</UserSecretsId>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<Folder Include="wwwroot\" />
12+
<Content Include="testCert.pfx" CopyToOutputDirectory="PreserveNewest" />
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<ProjectReference Include="..\..\src\Microsoft.AspNetCore\Microsoft.AspNetCore.csproj" />
17+
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="$(AspNetCoreVersion)" />
18+
</ItemGroup>
19+
20+
<ItemGroup>
21+
<DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="$(AspNetCoreVersion)" />
22+
</ItemGroup>
23+
24+
</Project>

samples/AppSettings/Program.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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;
6+
using Microsoft.AspNetCore.Http;
7+
8+
namespace AppSettings
9+
{
10+
public class Program
11+
{
12+
public static void Main(string[] args)
13+
{
14+
using (WebHost.Start(context => context.Response.WriteAsync("Hello, World!")))
15+
{
16+
Console.WriteLine("Running application: Press any key to shutdown...");
17+
Console.ReadKey();
18+
}
19+
}
20+
}
21+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"iisSettings": {
3+
"windowsAuthentication": false,
4+
"anonymousAuthentication": true,
5+
"iisExpress": {
6+
"applicationUrl": "http://localhost:53434/",
7+
"sslPort": 0
8+
}
9+
},
10+
"profiles": {
11+
"IIS Express": {
12+
"commandName": "IISExpress",
13+
"launchBrowser": true,
14+
"environmentVariables": {
15+
"ASPNETCORE_ENVIRONMENT": "Development"
16+
}
17+
},
18+
"AppSettings": {
19+
"commandName": "Project",
20+
"launchBrowser": true,
21+
"environmentVariables": {
22+
"ASPNETCORE_ENVIRONMENT": "Development"
23+
},
24+
"applicationUrl": "http://localhost:53435"
25+
}
26+
}
27+
}

samples/AppSettings/appsettings.json

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
{
2+
"Kestrel": {
3+
"EndPoints": {
4+
"Http": {
5+
"Address": "127.0.0.1",
6+
"Port": 8081
7+
},
8+
"HttpsInlineCertFile": {
9+
"Address": "127.0.0.1",
10+
"Port": 8082,
11+
"Certificate": {
12+
"Source": "File",
13+
"Path": "testCert.pfx",
14+
// TODO: remove when dotnet user-secrets is working again
15+
"Password": "testPassword"
16+
}
17+
},
18+
// Add testCert.pfx to the current user's certificate store to enable this scenario.
19+
// "HttpsInlineCertStore": {
20+
// "Address": "127.0.0.1",
21+
// "Port": 8083,
22+
// "Certificate": {
23+
// "Source": "Store",
24+
// "Subject": "localhost",
25+
// "StoreName": "My",
26+
// "StoreLocation": "CurrentUser"
27+
// }
28+
// },
29+
"HttpsCertFile": {
30+
"Address": "127.0.0.1",
31+
"Port": 8084,
32+
"Certificate": "TestCert"
33+
},
34+
// Add testCert.pfx to the current user's certificate store to enable this scenario.
35+
// "HttpsCertStore": {
36+
// "Address": "127.0.0.1",
37+
// "Port": 8085,
38+
// "Certificate": "TestCertInStore"
39+
// }
40+
}
41+
},
42+
"Certificates": {
43+
"TestCert": {
44+
"Source": "File",
45+
"Path": "testCert.pfx",
46+
// TODO: remove when dotnet user-secrets is working again
47+
"Password": "testPassword"
48+
},
49+
// Add testCert.pfx to the current user's certificate store to enable this scenario.
50+
// "TestCertInStore": {
51+
// "Source": "Store",
52+
// "Subject": "localhost",
53+
// "StoreName": "My",
54+
// "StoreLocation": "CurrentUser"
55+
// }
56+
}
57+
}

samples/AppSettings/testCert.pfx

2.42 KB
Binary file not shown.

samples/SampleApp/SampleApp.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk.Web">
22

3-
<Import Project="..\..\build\common.props" />
3+
<Import Project="..\..\build\dependencies.props" />
44

55
<PropertyGroup>
66
<TargetFramework>netcoreapp2.0</TargetFramework>
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
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 System.Collections.Generic;
6+
using System.Linq;
7+
using System.Security.Cryptography.X509Certificates;
8+
using Microsoft.Extensions.Configuration;
9+
10+
namespace Microsoft.AspNetCore
11+
{
12+
/// <summary>
13+
/// A helper class to load certificates from files and certificate stores based on <seealso cref="IConfiguration"/> data.
14+
/// </summary>
15+
public static class CertificateLoader
16+
{
17+
/// <summary>
18+
/// Loads one or more certificates from a single source.
19+
/// </summary>
20+
/// <param name="certificateConfiguration">An <seealso cref="IConfiguration"/> with information about a certificate source.</param>
21+
/// <param name="password">The certificate password, in case it's being loaded from a file.</param>
22+
/// <returns>The loaded certificates.</returns>
23+
public static X509Certificate2 Load(IConfiguration certificateConfiguration, string password = null)
24+
{
25+
var sourceKind = certificateConfiguration.GetValue<string>("Source");
26+
27+
CertificateSource certificateSource;
28+
switch (sourceKind)
29+
{
30+
case "File":
31+
certificateSource = new CertificateFileSource(password);
32+
break;
33+
case "Store":
34+
certificateSource = new CertificateStoreSource();
35+
break;
36+
default:
37+
throw new InvalidOperationException($"Invalid certificate source kind: {sourceKind}");
38+
}
39+
40+
certificateConfiguration.Bind(certificateSource);
41+
return certificateSource.Load();
42+
}
43+
44+
/// <summary>
45+
/// Loads all certificates specified in an <seealso cref="IConfiguration"/>.
46+
/// </summary>
47+
/// <param name="configurationRoot">The root <seealso cref="IConfiguration"/>.</param>
48+
/// <returns>
49+
/// A dictionary mapping certificate names to loaded certificates.
50+
/// </returns>
51+
public static Dictionary<string, X509Certificate2> LoadAll(IConfiguration configurationRoot)
52+
{
53+
var certificates = configurationRoot.GetSection("Certificates");
54+
var loadedCertificates = new Dictionary<string, X509Certificate2>();
55+
56+
foreach (var certificateSource in certificates.GetChildren())
57+
{
58+
var name = certificateSource.Key;
59+
loadedCertificates[name] = Load(certificateSource, configurationRoot[$"Certificates:{name}:Password"]);
60+
}
61+
62+
return loadedCertificates;
63+
}
64+
65+
private abstract class CertificateSource
66+
{
67+
public string Source { get; set; }
68+
69+
public abstract X509Certificate2 Load();
70+
}
71+
72+
private class CertificateFileSource : CertificateSource
73+
{
74+
private readonly string _password;
75+
76+
public CertificateFileSource(string password)
77+
{
78+
_password = password;
79+
}
80+
81+
public string Path { get; set; }
82+
83+
public override X509Certificate2 Load()
84+
{
85+
var certificate = TryLoad(X509KeyStorageFlags.DefaultKeySet, out var error)
86+
?? TryLoad(X509KeyStorageFlags.UserKeySet, out error)
87+
#if NETCOREAPP2_0
88+
?? TryLoad(X509KeyStorageFlags.EphemeralKeySet, out error)
89+
#endif
90+
;
91+
92+
if (error != null)
93+
{
94+
throw error;
95+
}
96+
97+
return certificate;
98+
}
99+
100+
private X509Certificate2 TryLoad(X509KeyStorageFlags flags, out Exception exception)
101+
{
102+
try
103+
{
104+
var loadedCertificate = new X509Certificate2(Path, _password);
105+
exception = null;
106+
return loadedCertificate;
107+
}
108+
catch (Exception e)
109+
{
110+
exception = e;
111+
return null;
112+
}
113+
}
114+
}
115+
116+
private class CertificateStoreSource : CertificateSource
117+
{
118+
public string Subject { get; set; }
119+
public string StoreName { get; set; }
120+
public string StoreLocation { get; set; }
121+
122+
public override X509Certificate2 Load()
123+
{
124+
if (!Enum.TryParse(StoreLocation, true, out StoreLocation storeLocation))
125+
{
126+
throw new InvalidOperationException($"Invalid store location: {StoreLocation}");
127+
}
128+
129+
using (var store = new X509Store(StoreName, storeLocation))
130+
{
131+
store.Open(OpenFlags.ReadOnly);
132+
var foundCertificate = store.Certificates.Find(X509FindType.FindBySubjectName, Subject, validOnly: false)
133+
.OfType<X509Certificate2>()
134+
.OrderByDescending(certificate => certificate.NotAfter)
135+
.First();
136+
137+
#if NET46
138+
store.Close();
139+
#endif
140+
141+
return foundCertificate;
142+
}
143+
}
144+
}
145+
}
146+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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 System.Collections.Generic;
6+
using System.Linq;
7+
using System.Net;
8+
using System.Security.Cryptography.X509Certificates;
9+
using Microsoft.AspNetCore.Hosting;
10+
using Microsoft.AspNetCore.Server.Kestrel.Core;
11+
using Microsoft.AspNetCore.Server.Kestrel.Https;
12+
using Microsoft.Extensions.Configuration;
13+
using Microsoft.Extensions.DependencyInjection;
14+
using Microsoft.Extensions.Options;
15+
16+
namespace Microsoft.AspNetCore
17+
{
18+
/// <summary>
19+
/// Binds Kestrel configuration.
20+
/// </summary>
21+
public class KestrelServerOptionsSetup : IConfigureOptions<KestrelServerOptions>
22+
{
23+
private IServiceProvider _services;
24+
25+
/// <summary>
26+
/// Creates a new instance of <see cref="KestrelServerOptionsSetup"/>.
27+
/// </summary>
28+
/// <param name="services">An <seealso cref="IServiceProvider"/> instance.</param>
29+
public KestrelServerOptionsSetup(IServiceProvider services)
30+
{
31+
_services = services;
32+
}
33+
34+
/// <summary>
35+
/// Configures a <seealso cref="KestrelServerOptions"/> instance.
36+
/// </summary>
37+
/// <param name="options">The <seealso cref="KestrelServerOptions"/> to configure.</param>
38+
public void Configure(KestrelServerOptions options)
39+
{
40+
options.ApplicationServices = _services;
41+
42+
var configuration = _services.GetService<IConfiguration>();
43+
BindConfiguration(options, configuration);
44+
}
45+
46+
private static void BindConfiguration(
47+
KestrelServerOptions options,
48+
IConfiguration configurationRoot)
49+
{
50+
var certificates = CertificateLoader.LoadAll(configurationRoot);
51+
var endPoints = configurationRoot.GetSection("Kestrel:EndPoints");
52+
53+
foreach (var endPoint in endPoints.GetChildren())
54+
{
55+
BindEndPoint(options, configurationRoot, endPoint, certificates);
56+
}
57+
}
58+
59+
private static void BindEndPoint(
60+
KestrelServerOptions options,
61+
IConfiguration configurationRoot,
62+
IConfigurationSection endPoint,
63+
Dictionary<string, X509Certificate2> certificates)
64+
{
65+
options.Listen(IPAddress.Parse(endPoint.GetValue<string>("Address")), endPoint.GetValue<int>("Port"), listenOptions =>
66+
{
67+
var certificateName = endPoint.GetValue<string>("Certificate");
68+
69+
X509Certificate2 endPointCertificate = null;
70+
if (certificateName != null)
71+
{
72+
endPointCertificate = certificates[certificateName];
73+
}
74+
else
75+
{
76+
var certificate = endPoint.GetSection("Certificate");
77+
78+
if (certificate.GetChildren().Any())
79+
{
80+
var password = configurationRoot[$"Kestrel:EndPoints:{endPoint.Key}:Certificate:Password"];
81+
endPointCertificate = CertificateLoader.Load(certificate, password);
82+
}
83+
}
84+
85+
if (endPointCertificate != null)
86+
{
87+
listenOptions.UseHttps(endPointCertificate);
88+
}
89+
});
90+
}
91+
}
92+
}

src/Microsoft.AspNetCore/Microsoft.AspNetCore.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
</PropertyGroup>
1212

1313
<ItemGroup>
14-
<PackageReference Include="@(MetaPackagePackageReference)" />
14+
<PackageReference Include="@(MetaPackagePackageReference)" />
1515
</ItemGroup>
1616

1717
</Project>

0 commit comments

Comments
 (0)