-
Notifications
You must be signed in to change notification settings - Fork 109
Bind Kestrel options to config by default (#30) #44
Changes from all commits
81d5f0d
a7528d2
ecaa630
f3d7cf5
fefe595
50e7904
48a59b6
e16e2af
290035c
04c8f6f
a4744a6
59626a7
23a16aa
b50c22b
6ea143c
ee4959c
88ef88e
9a1051d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<Project Sdk="Microsoft.NET.Sdk.Web"> | ||
|
||
<Import Project="..\..\build\dependencies.props" /> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>netcoreapp2.0</TargetFramework> | ||
<UserSecretsId>aspnetcore-MetaPackagesAppSettings-20170421155031</UserSecretsId> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<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> |
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(); | ||
} | ||
} | ||
} | ||
} |
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" | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
{ | ||
"Kestrel": { | ||
"EndPoints": { | ||
"Http": { | ||
"Address": "127.0.0.1", | ||
"Port": 8081 | ||
}, | ||
"HttpV6": { | ||
"Address": "::1", | ||
"Port": 8081 | ||
}, | ||
// Add testCert.pfx to the current user's certificate store to enable this scenario. | ||
//"HttpsInlineCertStore": { | ||
// "Address": "127.0.0.1", | ||
// "Port": 8082, | ||
// "Certificate": { | ||
// "Source": "Store", | ||
// "Subject": "cn=localhost", | ||
// "StoreName": "My", | ||
// "StoreLocation": "CurrentUser", | ||
// "AllowInvalid": "True" | ||
// } | ||
//}, | ||
"HttpsInlineCertFile": { | ||
"Address": "127.0.0.1", | ||
"Port": 8083, | ||
"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. | ||
//"HttpsCertStore": { | ||
// "Address": "127.0.0.1", | ||
// "Port": 8084, | ||
// "Certificate": "TestCertInStore" | ||
//}, | ||
"HttpsCertFile": { | ||
"Address": "127.0.0.1", | ||
"Port": 8085, | ||
"Certificate": "TestCert" | ||
} | ||
} | ||
}, | ||
"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": "cn=localhost", | ||
// "StoreName": "My", | ||
// "StoreLocation": "CurrentUser", | ||
// "AllowInvalid": "True" | ||
//} | ||
} | ||
} |
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" /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @natemcmaster pointed out we don't normally import |
||
|
||
<PropertyGroup> | ||
<TargetFramework>netcoreapp2.0</TargetFramework> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
// 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) | ||
{ | ||
var sourceKind = certificateConfiguration.GetValue<string>("Source"); | ||
|
||
CertificateSource certificateSource; | ||
switch (sourceKind.ToLowerInvariant()) | ||
{ | ||
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) | ||
{ | ||
return configurationRoot.GetSection("Certificates").GetChildren() | ||
.ToDictionary( | ||
certificateSource => certificateSource.Key, | ||
certificateSource => Load(certificateSource, certificateSource["Password"])); | ||
} | ||
|
||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What are you trying to do here? @bartonjs because he understands Ephemeral :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I read this as:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "perphemeral"? 🤣 Is there an advantage in trying "perphemeral" first, if ephermeral is there? There's no real need to persist the private key anyhere. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup, I wordsmithed. Assuming ephemeral works for your needs, it's probably the best choice. It takes disk IO and permissions completely out of play. The only concern is that if you pass the cert to something which directly calls CertGetCertificateContextProperty with CERT_KEY_PROV_INFO_PROP_ID they will be confused. But it avoids the whole we-leak-a-file-to-the-hard-drive-if-the-process-crashes annoyance of perphemeral (and the disk I/O // wear-and-tear). EphemeralKeySet doesn't work on macOS, though, so you'll still want to fall back to a perphemeral mode there. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll put the call with |
||
#endif | ||
; | ||
|
||
if (error != null) | ||
{ | ||
throw error; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rethrowing this exception looses the original stack trace. You should wrap it or use ExceptionDispatchInfo to capture and rethrow it. |
||
} | ||
|
||
return certificate; | ||
} | ||
|
||
private X509Certificate2 TryLoad(X509KeyStorageFlags flags, out Exception exception) | ||
{ | ||
try | ||
{ | ||
var loadedCertificate = new X509Certificate2(Path, _password, flags); | ||
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 bool AllowInvalid { get; set; } | ||
|
||
public override X509Certificate2 Load() | ||
{ | ||
if (!Enum.TryParse(StoreLocation, ignoreCase: true, result: out StoreLocation storeLocation)) | ||
{ | ||
throw new InvalidOperationException($"Invalid store location: {StoreLocation}"); | ||
} | ||
|
||
using (var store = new X509Store(StoreName, storeLocation)) | ||
{ | ||
X509Certificate2Collection storeCertificates = null; | ||
X509Certificate2Collection foundCertificates = null; | ||
X509Certificate2 foundCertificate = null; | ||
|
||
try | ||
{ | ||
store.Open(OpenFlags.ReadOnly); | ||
storeCertificates = store.Certificates; | ||
foundCertificates = storeCertificates.Find(X509FindType.FindBySubjectDistinguishedName, Subject, validOnly: !AllowInvalid); | ||
foundCertificate = foundCertificates | ||
.OfType<X509Certificate2>() | ||
.OrderByDescending(certificate => certificate.NotAfter) | ||
.FirstOrDefault(); | ||
|
||
if (foundCertificate == null) | ||
{ | ||
throw new InvalidOperationException($"No certificate found for {Subject} in store {StoreName} in {StoreLocation}"); | ||
} | ||
|
||
return foundCertificate; | ||
} | ||
finally | ||
{ | ||
if (foundCertificate != null) | ||
{ | ||
storeCertificates.Remove(foundCertificate); | ||
foundCertificates.Remove(foundCertificate); | ||
} | ||
|
||
DisposeCertificates(storeCertificates); | ||
DisposeCertificates(foundCertificates); | ||
} | ||
} | ||
} | ||
|
||
private void DisposeCertificates(X509Certificate2Collection certificates) | ||
{ | ||
if (certificates != null) | ||
{ | ||
foreach (var certificate in certificates) | ||
{ | ||
certificate.Dispose(); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not use the existing sample?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Settings would apply to all the scenarios there. I want to keep things separate for clarity.