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

Commit

Permalink
Bind Kestrel options to config by default (#30).
Browse files Browse the repository at this point in the history
  • Loading branch information
Cesar Blum Silveira committed Apr 25, 2017
1 parent 98b1945 commit 60e4917
Show file tree
Hide file tree
Showing 10 changed files with 372 additions and 2 deletions.
24 changes: 24 additions & 0 deletions samples/AppSettings/AppSettings.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<Import Project="..\..\build\dependencies.props" />

<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<UserSecretsId>aspnetcore-MetaPackagesSampleApp-20170421155031</UserSecretsId>
</PropertyGroup>

<ItemGroup>
<Folder Include="wwwroot\" />
<Content Include="testCert.pfx" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore\Microsoft.AspNetCore.csproj" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="$(AspNetCoreVersion)" />
</ItemGroup>

<ItemGroup>
<DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="$(AspNetCoreVersion)" />
</ItemGroup>

</Project>
21 changes: 21 additions & 0 deletions samples/AppSettings/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// 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;
using Microsoft.AspNetCore.Http;

namespace AppSettings
{
public class Program
{
public static void Main(string[] args)
{
using (WebHost.Start(context => context.Response.WriteAsync("Hello, World!")))
{
Console.WriteLine("Running application: Press any key to shutdown...");
Console.ReadKey();
}
}
}
}
27 changes: 27 additions & 0 deletions samples/AppSettings/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:53434/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"AppSettings": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:53435"
}
}
}
57 changes: 57 additions & 0 deletions samples/AppSettings/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"Kestrel": {
"EndPoints": {
"Http": {
"Address": "127.0.0.1",
"Port": 8081
},
"HttpsInlineCertFile": {
"Address": "127.0.0.1",
"Port": 8082,
"Certificate": {
"Source": "File",
"Path": "testCert.pfx",
// TODO: remove when dotnet user-secrets is working again
"Password": "testPassword"
}
},
// Add testCert.pfx to the current user's certificate store to enable this scenario.
// "HttpsInlineCertStore": {
// "Address": "127.0.0.1",
// "Port": 8083,
// "Certificate": {
// "Source": "Store",
// "Subject": "localhost",
// "StoreName": "My",
// "StoreLocation": "CurrentUser"
// }
// },
"HttpsCertFile": {
"Address": "127.0.0.1",
"Port": 8084,
"Certificate": "TestCert"
},
// Add testCert.pfx to the current user's certificate store to enable this scenario.
// "HttpsCertStore": {
// "Address": "127.0.0.1",
// "Port": 8085,
// "Certificate": "TestCertInStore"
// }
}
},
"Certificates": {
"TestCert": {
"Source": "File",
"Path": "testCert.pfx",
// TODO: remove when dotnet user-secrets is working again
"Password": "testPassword"
},
// Add testCert.pfx to the current user's certificate store to enable this scenario.
// "TestCertInStore": {
// "Source": "Store",
// "Subject": "localhost",
// "StoreName": "My",
// "StoreLocation": "CurrentUser"
// }
}
}
Binary file added samples/AppSettings/testCert.pfx
Binary file not shown.
2 changes: 1 addition & 1 deletion samples/SampleApp/SampleApp.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<Import Project="..\..\build\common.props" />
<Import Project="..\..\build\dependencies.props" />

<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
Expand Down
146 changes: 146 additions & 0 deletions src/Microsoft.AspNetCore/CertificateLoader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// 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 System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Extensions.Configuration;

namespace Microsoft.AspNetCore
{
/// <summary>
/// A helper class to load certificates from files and certificate stores based on <seealso cref="IConfiguration"/> data.
/// </summary>
public static class CertificateLoader
{
/// <summary>
/// Loads one or more certificates from a single source.
/// </summary>
/// <param name="certificateConfiguration">An <seealso cref="IConfiguration"/> with information about a certificate source.</param>
/// <param name="password">The certificate password, in case it's being loaded from a file.</param>
/// <returns>The loaded certificates.</returns>
public static X509Certificate2 Load(IConfiguration certificateConfiguration, string password = null)
{
var sourceKind = certificateConfiguration.GetValue<string>("Source");

CertificateSource certificateSource;
switch (sourceKind)
{
case "File":
certificateSource = new CertificateFileSource(password);
break;
case "Store":
certificateSource = new CertificateStoreSource();
break;
default:
throw new InvalidOperationException($"Invalid certificate source kind: {sourceKind}");
}

certificateConfiguration.Bind(certificateSource);
return certificateSource.Load();
}

/// <summary>
/// Loads all certificates specified in an <seealso cref="IConfiguration"/>.
/// </summary>
/// <param name="configurationRoot">The root <seealso cref="IConfiguration"/>.</param>
/// <returns>
/// A dictionary mapping certificate names to loaded certificates.
/// </returns>
public static Dictionary<string, X509Certificate2> LoadAll(IConfiguration configurationRoot)
{
var certificates = configurationRoot.GetSection("Certificates");
var loadedCertificates = new Dictionary<string, X509Certificate2>();

foreach (var certificateSource in certificates.GetChildren())
{
var name = certificateSource.Key;
loadedCertificates[name] = Load(certificateSource, configurationRoot[$"Certificates:{name}:Password"]);
}

return loadedCertificates;
}

private abstract class CertificateSource
{
public string Source { get; set; }

public abstract X509Certificate2 Load();
}

private class CertificateFileSource : CertificateSource
{
private readonly string _password;

public CertificateFileSource(string password)
{
_password = password;
}

public string Path { get; set; }

public override X509Certificate2 Load()
{
var certificate = TryLoad(X509KeyStorageFlags.DefaultKeySet, out var error)
?? TryLoad(X509KeyStorageFlags.UserKeySet, out error)
#if NETCOREAPP2_0
?? TryLoad(X509KeyStorageFlags.EphemeralKeySet, out error)
#endif
;

if (error != null)
{
throw error;
}

return certificate;
}

private X509Certificate2 TryLoad(X509KeyStorageFlags flags, out Exception exception)
{
try
{
var loadedCertificate = new X509Certificate2(Path, _password);
exception = null;
return loadedCertificate;
}
catch (Exception e)
{
exception = e;
return null;
}
}
}

private class CertificateStoreSource : CertificateSource
{
public string Subject { get; set; }
public string StoreName { get; set; }
public string StoreLocation { get; set; }

public override X509Certificate2 Load()
{
if (!Enum.TryParse(StoreLocation, true, out StoreLocation storeLocation))
{
throw new InvalidOperationException($"Invalid store location: {StoreLocation}");
}

using (var store = new X509Store(StoreName, storeLocation))
{
store.Open(OpenFlags.ReadOnly);
var foundCertificate = store.Certificates.Find(X509FindType.FindBySubjectName, Subject, validOnly: false)
.OfType<X509Certificate2>()
.OrderByDescending(certificate => certificate.NotAfter)
.First();

#if NET46
store.Close();
#endif

return foundCertificate;
}
}
}
}
}
92 changes: 92 additions & 0 deletions src/Microsoft.AspNetCore/KestrelServerOptionsSetup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// 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 System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace Microsoft.AspNetCore
{
/// <summary>
/// Binds Kestrel configuration.
/// </summary>
public class KestrelServerOptionsSetup : IConfigureOptions<KestrelServerOptions>
{
private IServiceProvider _services;

/// <summary>
/// Creates a new instance of <see cref="KestrelServerOptionsSetup"/>.
/// </summary>
/// <param name="services">An <seealso cref="IServiceProvider"/> instance.</param>
public KestrelServerOptionsSetup(IServiceProvider services)
{
_services = services;
}

/// <summary>
/// Configures a <seealso cref="KestrelServerOptions"/> instance.
/// </summary>
/// <param name="options">The <seealso cref="KestrelServerOptions"/> to configure.</param>
public void Configure(KestrelServerOptions options)
{
options.ApplicationServices = _services;

var configuration = _services.GetService<IConfiguration>();
BindConfiguration(options, configuration);
}

private static void BindConfiguration(
KestrelServerOptions options,
IConfiguration configurationRoot)
{
var certificates = CertificateLoader.LoadAll(configurationRoot);
var endPoints = configurationRoot.GetSection("Kestrel:EndPoints");

foreach (var endPoint in endPoints.GetChildren())
{
BindEndPoint(options, configurationRoot, endPoint, certificates);
}
}

private static void BindEndPoint(
KestrelServerOptions options,
IConfiguration configurationRoot,
IConfigurationSection endPoint,
Dictionary<string, X509Certificate2> certificates)
{
options.Listen(IPAddress.Parse(endPoint.GetValue<string>("Address")), endPoint.GetValue<int>("Port"), listenOptions =>
{
var certificateName = endPoint.GetValue<string>("Certificate");

X509Certificate2 endPointCertificate = null;
if (certificateName != null)
{
endPointCertificate = certificates[certificateName];
}
else
{
var certificate = endPoint.GetSection("Certificate");

if (certificate.GetChildren().Any())
{
var password = configurationRoot[$"Kestrel:EndPoints:{endPoint.Key}:Certificate:Password"];
endPointCertificate = CertificateLoader.Load(certificate, password);
}
}

if (endPointCertificate != null)
{
listenOptions.UseHttps(endPointCertificate);
}
});
}
}
}
2 changes: 1 addition & 1 deletion src/Microsoft.AspNetCore/Microsoft.AspNetCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="@(MetaPackagePackageReference)" />
<PackageReference Include="@(MetaPackagePackageReference)" />
</ItemGroup>

</Project>
Loading

0 comments on commit 60e4917

Please sign in to comment.