Implement config support #1290 #1879 #2016 #2166 #2167 #2188

This commit is contained in:
Chris Ross (ASP.NET) 2017-11-21 10:59:34 -08:00
parent 3a0a133a02
commit dfaf37cbba
46 changed files with 1903 additions and 399 deletions

View File

@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27110.0
VisualStudioVersion = 15.0.27130.2010
MinimumVisualStudioVersion = 15.0.26730.03
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7972A5D6-3385-4127-9277-428506DD44FF}"
ProjectSection(SolutionItems) = preProject
@ -104,6 +104,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Protocols.Abstractions", "s
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{C2910A13-B2C2-46D8-81D8-7E166F4F5981}"
ProjectSection(SolutionItems) = preProject
build\dependencies.props = build\dependencies.props
build\repo.props = build\repo.props
build\repo.targets = build\repo.targets
EndProjectSection
@ -119,7 +120,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kestrel.Transport.Libuv.Fun
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kestrel.Transport.Sockets.FunctionalTests", "test\Kestrel.Transport.Sockets.FunctionalTests\Kestrel.Transport.Sockets.FunctionalTests.csproj", "{9C7B6B5F-088A-436E-834B-6373EA36DEEE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Http2SampleApp", "samples\Http2SampleApp\Http2SampleApp.csproj", "{7BC22A4A-15D2-44C2-AB45-049F0FB562FA}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Http2SampleApp", "samples\Http2SampleApp\Http2SampleApp.csproj", "{7BC22A4A-15D2-44C2-AB45-049F0FB562FA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SystemdTestApp", "samples\SystemdTestApp\SystemdTestApp.csproj", "{A7994A41-CAF8-47A7-8975-F101F75B5BC1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -335,6 +338,18 @@ Global
{7BC22A4A-15D2-44C2-AB45-049F0FB562FA}.Release|x64.Build.0 = Release|Any CPU
{7BC22A4A-15D2-44C2-AB45-049F0FB562FA}.Release|x86.ActiveCfg = Release|Any CPU
{7BC22A4A-15D2-44C2-AB45-049F0FB562FA}.Release|x86.Build.0 = Release|Any CPU
{A7994A41-CAF8-47A7-8975-F101F75B5BC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A7994A41-CAF8-47A7-8975-F101F75B5BC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A7994A41-CAF8-47A7-8975-F101F75B5BC1}.Debug|x64.ActiveCfg = Debug|Any CPU
{A7994A41-CAF8-47A7-8975-F101F75B5BC1}.Debug|x64.Build.0 = Debug|Any CPU
{A7994A41-CAF8-47A7-8975-F101F75B5BC1}.Debug|x86.ActiveCfg = Debug|Any CPU
{A7994A41-CAF8-47A7-8975-F101F75B5BC1}.Debug|x86.Build.0 = Debug|Any CPU
{A7994A41-CAF8-47A7-8975-F101F75B5BC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A7994A41-CAF8-47A7-8975-F101F75B5BC1}.Release|Any CPU.Build.0 = Release|Any CPU
{A7994A41-CAF8-47A7-8975-F101F75B5BC1}.Release|x64.ActiveCfg = Release|Any CPU
{A7994A41-CAF8-47A7-8975-F101F75B5BC1}.Release|x64.Build.0 = Release|Any CPU
{A7994A41-CAF8-47A7-8975-F101F75B5BC1}.Release|x86.ActiveCfg = Release|Any CPU
{A7994A41-CAF8-47A7-8975-F101F75B5BC1}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -359,6 +374,7 @@ Global
{74032D79-8EA7-4483-BD82-C38370420FFF} = {D3273454-EA07-41D2-BF0B-FCC3675C2483}
{9C7B6B5F-088A-436E-834B-6373EA36DEEE} = {D3273454-EA07-41D2-BF0B-FCC3675C2483}
{7BC22A4A-15D2-44C2-AB45-049F0FB562FA} = {8A3D00B8-1CCF-4BE6-A060-11104CE2D9CE}
{A7994A41-CAF8-47A7-8975-F101F75B5BC1} = {8A3D00B8-1CCF-4BE6-A060-11104CE2D9CE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2D10D020-6770-47CA-BB8D-2C23FE3AE071}

View File

@ -16,6 +16,9 @@
<MicrosoftAspNetCoreHttpPackageVersion>2.1.0-preview1-27845</MicrosoftAspNetCoreHttpPackageVersion>
<MicrosoftAspNetCoreTestingPackageVersion>2.1.0-preview1-27845</MicrosoftAspNetCoreTestingPackageVersion>
<MicrosoftAspNetCoreWebUtilitiesPackageVersion>2.1.0-preview1-27845</MicrosoftAspNetCoreWebUtilitiesPackageVersion>
<MicrosoftExtensionsConfigurationBinderPackageVersion>2.1.0-preview1-27845</MicrosoftExtensionsConfigurationBinderPackageVersion>
<MicrosoftExtensionsConfigurationJsonPackageVersion>2.1.0-preview1-27845</MicrosoftExtensionsConfigurationJsonPackageVersion>
<MicrosoftExtensionsDependencyInjectionPackageVersion>2.1.0-preview1-27845</MicrosoftExtensionsDependencyInjectionPackageVersion>
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.1.0-preview1-27845</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
<MicrosoftExtensionsLoggingConsolePackageVersion>2.1.0-preview1-27845</MicrosoftExtensionsLoggingConsolePackageVersion>
<MicrosoftExtensionsLoggingPackageVersion>2.1.0-preview1-27845</MicrosoftExtensionsLoggingPackageVersion>

View File

@ -12,11 +12,26 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="$(MicrosoftExtensionsConfigurationJsonPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
</ItemGroup>
<ItemGroup>
<Content Include="../../test/shared/TestCertificates/testCert.pfx" CopyToOutputDirectory="PreserveNewest" />
<Content Include="testCert.pfx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Content Update="appsettings.Development.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="appsettings.Production.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@ -3,13 +3,15 @@
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
@ -48,10 +50,31 @@ namespace SampleApp
{
factory.AddConsole();
})
.ConfigureAppConfiguration((hostingContext, config) =>
{
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
})
.UseKestrel((context, options) =>
{
if (context.HostingEnvironment.IsDevelopment())
{
ShowConfig(context.Configuration);
}
var basePort = context.Configuration.GetValue<int?>("BASE_PORT") ?? 5000;
options.ConfigureEndpointDefaults(opt =>
{
opt.Protocols = HttpProtocols.Http1;
});
options.ConfigureHttpsDefaults(httpsOptions =>
{
httpsOptions.SslProtocols = SslProtocols.Tls12;
});
// Run callbacks on the transport thread
options.ApplicationSchedulingMode = SchedulingMode.Inline;
@ -71,11 +94,34 @@ namespace SampleApp
options.ListenLocalhost(basePort + 2, listenOptions =>
{
listenOptions.UseHttps("testCert.pfx", "testPassword");
// Use default dev cert
listenOptions.UseHttps();
});
options.ListenAnyIP(basePort + 3);
options.ListenAnyIP(basePort + 4, listenOptions =>
{
listenOptions.UseHttps(StoreName.My, "aspnet.test", allowInvalid: true);
});
options
.Configure()
.Endpoint(IPAddress.Loopback, basePort + 5)
.LocalhostEndpoint(basePort + 6)
.Load();
options
.Configure(context.Configuration.GetSection("Kestrel"))
.Endpoint("NamedEndpoint", opt =>
{
opt.ListenOptions.Protocols = HttpProtocols.Http1;
})
.Endpoint("NamedHttpsEndpoint", opt =>
{
opt.HttpsOptions.SslProtocols = SslProtocols.Tls12;
});
options.UseSystemd();
// The following section should be used to demo sockets
@ -96,5 +142,14 @@ namespace SampleApp
return hostBuilder.Build().RunAsync();
}
private static void ShowConfig(IConfiguration config)
{
foreach (var pair in config.GetChildren())
{
Console.WriteLine($"{pair.Path} - {pair.Value}");
ShowConfig(pair);
}
}
}
}

View File

@ -0,0 +1,15 @@
{
"Kestrel": {
"Endpoints": {
"NamedEndpoint": { "Url": "http://localhost:6000" },
"NamedHttpsEndpoint": {
"Url": "https://localhost:6443",
"Certificate": {
"Subject": "aspnet.test",
"Store": "My",
"AllowInvalid": true
}
}
}
}
}

View File

@ -0,0 +1,14 @@
{
"Kestrel": {
"Endpoints": {
"NamedEndpoint": { "Url": "http://*:6000" },
"NamedHttpsEndpoint": {
"Url": "https://*:6443",
"Certificate": {
"Path": "testCert.pfx",
"Password": "testPassword"
}
}
}
}
}

View File

@ -0,0 +1,6 @@
{
"Kestrel": {
"Endpoints": {
}
}
}

View File

@ -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.Diagnostics;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace SystemdTestApp
{
public class Startup
{
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
var logger = loggerFactory.CreateLogger("Default");
app.Run(async context =>
{
var connectionFeature = context.Connection;
logger.LogDebug($"Peer: {connectionFeature.RemoteIpAddress?.ToString()}:{connectionFeature.RemotePort}"
+ $"{Environment.NewLine}"
+ $"Sock: {connectionFeature.LocalIpAddress?.ToString()}:{connectionFeature.LocalPort}");
var response = $"hello, world{Environment.NewLine}";
context.Response.ContentLength = response.Length;
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync(response);
});
}
public static Task Main(string[] args)
{
TaskScheduler.UnobservedTaskException += (sender, e) =>
{
Console.WriteLine("Unobserved exception: {0}", e.Exception);
};
var hostBuilder = new WebHostBuilder()
.ConfigureLogging((_, factory) =>
{
factory.AddConsole();
})
.UseKestrel((context, options) =>
{
var basePort = context.Configuration.GetValue<int?>("BASE_PORT") ?? 5000;
// Run callbacks on the transport thread
options.ApplicationSchedulingMode = SchedulingMode.Inline;
options.Listen(IPAddress.Loopback, basePort, listenOptions =>
{
// Uncomment the following to enable Nagle's algorithm for this endpoint.
//listenOptions.NoDelay = false;
listenOptions.UseConnectionLogging();
});
options.Listen(IPAddress.Loopback, basePort + 1, listenOptions =>
{
listenOptions.UseHttps("testCert.pfx", "testPassword");
listenOptions.UseConnectionLogging();
});
options.UseSystemd();
// The following section should be used to demo sockets
//options.ListenUnixSocket("/tmp/kestrel-test.sock");
})
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>();
if (string.Equals(Process.GetCurrentProcess().Id.ToString(), Environment.GetEnvironmentVariable("LISTEN_PID")))
{
// Use libuv if activated by systemd, since that's currently the only transport that supports being passed a socket handle.
hostBuilder.UseLibuv(options =>
{
// Uncomment the following line to change the default number of libuv threads for all endpoints.
// options.ThreadCount = 4;
});
}
return hostBuilder.Build().RunAsync();
}
}
}

View File

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.1;netcoreapp2.0;net461</TargetFrameworks>
<IsPackable>false</IsPackable>
<NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Kestrel\Kestrel.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
</ItemGroup>
<ItemGroup>
<Content Include="testCert.pfx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

Binary file not shown.

View File

@ -480,4 +480,19 @@
<data name="ServiceCertificateRequired" xml:space="preserve">
<value>The server certificate parameter is required.</value>
</data>
<data name="BindingToDefaultAddresses" xml:space="preserve">
<value>No listening endpoints were configured. Binding to {address0} and {address1} by default.</value>
</data>
<data name="CertNotFoundInStore" xml:space="preserve">
<value>The requested certificate {subject} could not be found in {storeLocation}/{storeName} with AllowInvalid setting: {allowInvalid}.</value>
</data>
<data name="EndpointMissingUrl" xml:space="preserve">
<value>The endpoint {endpointName} is missing the required 'Url' parameter.</value>
</data>
<data name="NoCertSpecifiedNoDevelopmentCertificateFound" xml:space="preserve">
<value>Unable to configure HTTPS endpoint. No server certificate was specified and the default developer certificate could not be found. Try running 'dotnet developercertificates https -t' to setup a developer certificate for use with localhost. For information on configuring HTTPS see https://go.microsoft.com/fwlink/?linkid=848054</value>
</data>
<data name="MultipleCertificateSources" xml:space="preserve">
<value>The endpoint {endpointName} specified multiple certificate sources.</value>
</data>
</root>

View File

@ -0,0 +1,26 @@
// 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.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.Extensions.Configuration;
namespace Microsoft.AspNetCore.Server.Kestrel
{
public class EndpointConfiguration
{
internal EndpointConfiguration(bool isHttps, ListenOptions listenOptions, HttpsConnectionAdapterOptions httpsOptions, IConfigurationSection configSection)
{
IsHttps = isHttps;
ListenOptions = listenOptions ?? throw new ArgumentNullException(nameof(listenOptions));
HttpsOptions = httpsOptions ?? throw new ArgumentNullException(nameof(httpsOptions));
ConfigSection = configSection ?? throw new ArgumentNullException(nameof(configSection));
}
public bool IsHttps { get; }
public ListenOptions ListenOptions { get; }
public HttpsConnectionAdapterOptions HttpsOptions { get; }
public IConfigurationSection ConfigSection { get; }
}
}

View File

@ -14,7 +14,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public List<ListenOptions> ListenOptions { get; set; }
public KestrelServerOptions ServerOptions { get; set; }
public ILogger Logger { get; set; }
public IDefaultHttpsProvider DefaultHttpsProvider { get; set; }
public Func<ListenOptions, Task> CreateBinding { get; set; }
}

View File

@ -8,6 +8,7 @@ using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Protocols;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
@ -20,7 +21,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public static async Task BindAsync(IServerAddressesFeature addresses,
KestrelServerOptions serverOptions,
ILogger logger,
IDefaultHttpsProvider defaultHttpsProvider,
Func<ListenOptions, Task> createBinding)
{
var listenOptions = serverOptions.ListenOptions;
@ -35,7 +35,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
ListenOptions = listenOptions,
ServerOptions = serverOptions,
Logger = logger,
DefaultHttpsProvider = defaultHttpsProvider ?? UnconfiguredDefaultHttpsProvider.Instance,
CreateBinding = createBinding
};
@ -112,10 +111,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
context.ListenOptions.Add(endpoint);
}
internal static ListenOptions ParseAddress(string address, KestrelServerOptions serverOptions, IDefaultHttpsProvider defaultHttpsProvider)
internal static ListenOptions ParseAddress(string address, out bool https)
{
var parsedAddress = ServerAddress.FromUrl(address);
var https = false;
https = false;
if (parsedAddress.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase))
{
@ -151,12 +150,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
options = new AnyIPListenOptions(parsedAddress.Port);
}
if (https)
{
options.KestrelServerOptions = serverOptions;
defaultHttpsProvider.ConfigureHttps(options);
}
return options;
}
@ -169,10 +162,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{
public async Task BindAsync(AddressBindContext context)
{
context.Logger.LogDebug(CoreStrings.BindingToDefaultAddress, Constants.DefaultServerAddress);
var httpDefault = ParseAddress(Constants.DefaultServerAddress, out var https);
context.ServerOptions.ApplyEndpointDefaults(httpDefault);
await httpDefault.BindAsync(context).ConfigureAwait(false);
await ParseAddress(Constants.DefaultServerAddress, context.ServerOptions, context.DefaultHttpsProvider)
.BindAsync(context).ConfigureAwait(false);
// Conditional https default, only if a cert is available
var httpsDefault = ParseAddress(Constants.DefaultServerHttpsAddress, out https);
context.ServerOptions.ApplyEndpointDefaults(httpsDefault);
if (httpsDefault.ConnectionAdapters.Any(f => f.IsHttps)
|| httpsDefault.TryUseHttps())
{
await httpsDefault.BindAsync(context).ConfigureAwait(false);
context.Logger.LogDebug(CoreStrings.BindingToDefaultAddresses,
Constants.DefaultServerAddress, Constants.DefaultServerHttpsAddress);
}
else
{
// No default cert is available, do not bind to the https endpoint.
context.Logger.LogDebug(CoreStrings.BindingToDefaultAddress, Constants.DefaultServerAddress);
}
}
}
@ -242,27 +251,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{
foreach (var address in _addresses)
{
await ParseAddress(address, context.ServerOptions, context.DefaultHttpsProvider)
.BindAsync(context).ConfigureAwait(false);
var options = ParseAddress(address, out var https);
context.ServerOptions.ApplyEndpointDefaults(options);
if (https && !options.ConnectionAdapters.Any(f => f.IsHttps))
{
options.UseHttps();
}
await options.BindAsync(context).ConfigureAwait(false);
}
}
}
private class UnconfiguredDefaultHttpsProvider : IDefaultHttpsProvider
{
public static readonly UnconfiguredDefaultHttpsProvider Instance = new UnconfiguredDefaultHttpsProvider();
private UnconfiguredDefaultHttpsProvider()
{
}
public void ConfigureHttps(ListenOptions listenOptions)
{
// We have to throw here. If this is called, it's because the user asked for "https" binding but for some
// reason didn't provide a certificate and didn't use the "DefaultHttpsProvider". This means if we no-op,
// we'll silently downgrade to HTTP, which is bad.
throw new InvalidOperationException(CoreStrings.UnableToConfigureHttpsBindings);
}
}
}
}

View File

@ -0,0 +1,99 @@
// 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 System.Text;
using Microsoft.AspNetCore.Server.Kestrel.Core;
namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
{
public static class CertificateLoader
{
// See http://oid-info.com/get/1.3.6.1.5.5.7.3.1
// Indicates that a certificate can be used as a SSL server certificate
private const string ServerAuthenticationOid = "1.3.6.1.5.5.7.3.1";
public static X509Certificate2 LoadFromStoreCert(string subject, string storeName, StoreLocation storeLocation, bool allowInvalid)
{
using (var store = new X509Store(storeName, storeLocation))
{
X509Certificate2Collection storeCertificates = null;
X509Certificate2 foundCertificate = null;
try
{
store.Open(OpenFlags.ReadOnly);
storeCertificates = store.Certificates;
var foundCertificates = storeCertificates.Find(X509FindType.FindBySubjectName, subject, !allowInvalid);
foundCertificate = foundCertificates
.OfType<X509Certificate2>()
.Where(IsCertificateAllowedForServerAuth)
.OrderByDescending(certificate => certificate.NotAfter)
.FirstOrDefault();
if (foundCertificate == null)
{
throw new InvalidOperationException(CoreStrings.FormatCertNotFoundInStore(subject, storeLocation, storeName, allowInvalid));
}
return foundCertificate;
}
finally
{
DisposeCertificates(storeCertificates, except: foundCertificate);
}
}
}
internal static bool IsCertificateAllowedForServerAuth(X509Certificate2 certificate)
{
/* If the Extended Key Usage extension is included, then we check that the serverAuth usage is included. (http://oid-info.com/get/1.3.6.1.5.5.7.3.1)
* If the Extended Key Usage extension is not included, then we assume the certificate is allowed for all usages.
*
* See also https://blogs.msdn.microsoft.com/kaushal/2012/02/17/client-certificates-vs-server-certificates/
*
* From https://tools.ietf.org/html/rfc3280#section-4.2.1.13 "Certificate Extensions: Extended Key Usage"
*
* If the (Extended Key Usage) extension is present, then the certificate MUST only be used
* for one of the purposes indicated. If multiple purposes are
* indicated the application need not recognize all purposes indicated,
* as long as the intended purpose is present. Certificate using
* applications MAY require that a particular purpose be indicated in
* order for the certificate to be acceptable to that application.
*/
var hasEkuExtension = false;
foreach (var extension in certificate.Extensions.OfType<X509EnhancedKeyUsageExtension>())
{
hasEkuExtension = true;
foreach (var oid in extension.EnhancedKeyUsages)
{
if (oid.Value.Equals(ServerAuthenticationOid, StringComparison.Ordinal))
{
return true;
}
}
}
return !hasEkuExtension;
}
private static void DisposeCertificates(X509Certificate2Collection certificates, X509Certificate2 except)
{
if (certificates != null)
{
foreach (var certificate in certificates)
{
if (!certificate.Equals(except))
{
certificate.Dispose();
}
}
}
}
}
}

View File

@ -0,0 +1,140 @@
// 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 Microsoft.Extensions.Configuration;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{
internal class ConfigurationReader
{
private IConfiguration _configuration;
private IDictionary<string, CertificateConfig> _certificates;
private IList<EndpointConfig> _endpoints;
public ConfigurationReader(IConfiguration configuration)
{
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
}
public IDictionary<string, CertificateConfig> Certificates
{
get
{
if (_certificates == null)
{
ReadCertificates();
}
return _certificates;
}
}
public IEnumerable<EndpointConfig> Endpoints
{
get
{
if (_endpoints == null)
{
ReadEndpoints();
}
return _endpoints;
}
}
private void ReadCertificates()
{
_certificates = new Dictionary<string, CertificateConfig>(0);
var certificatesConfig = _configuration.GetSection("Certificates").GetChildren();
foreach (var certificateConfig in certificatesConfig)
{
_certificates.Add(certificateConfig.Key, new CertificateConfig(certificateConfig));
}
}
private void ReadEndpoints()
{
_endpoints = new List<EndpointConfig>();
var endpointsConfig = _configuration.GetSection("Endpoints").GetChildren();
foreach (var endpointConfig in endpointsConfig)
{
// "EndpointName": {
        // "Url": "https://*:5463",
        // "Certificate": {
          // "Path": "testCert.pfx",
          // "Password": "testPassword"
       // }
// }
var url = endpointConfig["Url"];
if (string.IsNullOrEmpty(url))
{
throw new InvalidOperationException(CoreStrings.FormatEndpointMissingUrl(endpointConfig.Key));
}
var endpoint = new EndpointConfig()
{
Name = endpointConfig.Key,
Url = url,
ConfigSection = endpointConfig,
Certificate = new CertificateConfig(endpointConfig.GetSection("Certificate")),
};
_endpoints.Add(endpoint);
}
}
}
// "EndpointName": {
// "Url": "https://*:5463",
// "Certificate": {
// "Path": "testCert.pfx",
// "Password": "testPassword"
// }
// }
internal class EndpointConfig
{
public string Name { get; set; }
public string Url { get; set; }
public IConfigurationSection ConfigSection { get; set; }
public CertificateConfig Certificate { get; set; }
}
// "CertificateName": {
// "Path": "testCert.pfx",
// "Password": "testPassword"
// }
internal class CertificateConfig
{
public CertificateConfig(IConfigurationSection configSection)
{
ConfigSection = configSection;
ConfigSection.Bind(this);
}
public IConfigurationSection ConfigSection { get; }
// File
public bool IsFileCert => !string.IsNullOrEmpty(Path);
public string Path { get; set; }
public string Password { get; set; }
// Cert store
public bool IsStoreCert => !string.IsNullOrEmpty(Subject);
public string Subject { get; set; }
public string Store { get; set; }
public string Location { get; set; }
public bool? AllowInvalid { get; set; }
}
}

View File

@ -19,10 +19,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
{
public class HttpsConnectionAdapter : IConnectionAdapter
{
// See http://oid-info.com/get/1.3.6.1.5.5.7.3.1
// Indicates that a certificate can be used as a SSL server certificate
private const string ServerAuthenticationOid = "1.3.6.1.5.5.7.3.1";
private static readonly ClosedAdaptedConnection _closedAdaptedConnection = new ClosedAdaptedConnection();
private readonly HttpsConnectionAdapterOptions _options;
@ -187,36 +183,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
private static void EnsureCertificateIsAllowedForServerAuth(X509Certificate2 certificate)
{
/* If the Extended Key Usage extension is included, then we check that the serverAuth usage is included. (http://oid-info.com/get/1.3.6.1.5.5.7.3.1)
* If the Extended Key Usage extension is not included, then we assume the certificate is allowed for all usages.
*
* See also https://blogs.msdn.microsoft.com/kaushal/2012/02/17/client-certificates-vs-server-certificates/
*
* From https://tools.ietf.org/html/rfc3280#section-4.2.1.13 "Certificate Extensions: Extended Key Usage"
*
* If the (Extended Key Usage) extension is present, then the certificate MUST only be used
* for one of the purposes indicated. If multiple purposes are
* indicated the application need not recognize all purposes indicated,
* as long as the intended purpose is present. Certificate using
* applications MAY require that a particular purpose be indicated in
* order for the certificate to be acceptable to that application.
*/
var hasEkuExtension = false;
foreach (var extension in certificate.Extensions.OfType<X509EnhancedKeyUsageExtension>())
{
hasEkuExtension = true;
foreach (var oid in extension.EnhancedKeyUsages)
{
if (oid.Value.Equals(ServerAuthenticationOid, StringComparison.Ordinal))
{
return;
}
}
}
if (hasEkuExtension)
if (!CertificateLoader.IsCertificateAllowedForServerAuth(certificate))
{
throw new InvalidOperationException(CoreStrings.FormatInvalidServerCertificateEku(certificate.Thumbprint));
}

View File

@ -1,10 +0,0 @@
// 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.
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{
public interface IDefaultHttpsProvider
{
void ConfigureHttps(ListenOptions listenOptions);
}
}

View File

@ -10,10 +10,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
public const int MaxExceptionDetailSize = 128;
/// <summary>
/// The IPEndPoint Kestrel will bind to if nothing else is specified.
/// The endpoint Kestrel will bind to if nothing else is specified.
/// </summary>
public static readonly string DefaultServerAddress = "http://localhost:5000";
/// <summary>
/// The endpoint Kestrel will bind to if nothing else is specified and a default certificate is available.
/// </summary>
public static readonly string DefaultServerHttpsAddress = "https://localhost:5001";
/// <summary>
/// Prefix of host name used to specify Unix sockets in the configuration.
/// </summary>

View File

@ -2,8 +2,12 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Hosting.Server.Features;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Certificates.Generation;
using Microsoft.AspNetCore.Server.Kestrel.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
@ -20,6 +24,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public void Configure(KestrelServerOptions options)
{
options.ApplicationServices = _services;
UseDefaultDeveloperCertificate(options);
}
private void UseDefaultDeveloperCertificate(KestrelServerOptions options)
{
var certificateManager = new CertificateManager();
var certificate = certificateManager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: true)
.FirstOrDefault();
var logger = options.ApplicationServices.GetRequiredService<ILogger<KestrelServer>>();
if (certificate != null)
{
logger.LocatedDevelopmentCertificate(certificate);
options.DefaultCertificate = certificate;
}
else
{
logger.UnableToLocateDevelopmentCertificate();
}
}
}
}

View File

@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
LoggerMessage.Define<string, string>(LogLevel.Debug, new EventId(0, nameof(LocatedDevelopmentCertificate)), "Using development certificate: {certificateSubjectName} (Thumbprint: {certificateThumbprint})");
private static readonly Action<ILogger, Exception> _unableToLocateDevelopmentCertificate =
LoggerMessage.Define(LogLevel.Error, new EventId(1, nameof(UnableToLocateDevelopmentCertificate)), "Unable to locate an appropriate development https certificate.");
LoggerMessage.Define(LogLevel.Debug, new EventId(1, nameof(UnableToLocateDevelopmentCertificate)), "Unable to locate an appropriate development https certificate.");
public static void LocatedDevelopmentCertificate(this ILogger logger, X509Certificate2 certificate) => _locatedDevelopmentCertificate(logger, certificate.Subject, certificate.Thumbprint, null);

View File

@ -12,14 +12,17 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Certificates.Generation.Sources" PrivateAssets="All" Version="$(MicrosoftAspNetCoreCertificatesGenerationSourcesPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="$(MicrosoftAspNetCoreHostingAbstractionsPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="$(MicrosoftAspNetCoreWebUtilitiesPackageVersion)" />
<PackageReference Include="Microsoft.Net.Http.Headers" Version="$(MicrosoftNetHttpHeadersPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="$(MicrosoftExtensionsConfigurationBinderPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftExtensionsLoggingAbstractionsPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Options" Version="$(MicrosoftExtensionsOptionsPackageVersion)" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="$(SystemThreadingTasksExtensionsPackageVersion)" />
<PackageReference Include="System.Memory" Version="$(SystemMemoryPackageVersion)" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="$(SystemRuntimeCompilerServicesUnsafePackageVersion)" />
<PackageReference Include="System.Security.Cryptography.Cng" Version="$(SystemSecurityCryptographyCngPackageVersion)" />
</ItemGroup>
<ItemGroup>

View File

@ -0,0 +1,297 @@
// 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.IO;
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.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.AspNetCore.Server.Kestrel.Https.Internal;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Server.Kestrel
{
public class KestrelConfigurationLoader
{
internal KestrelConfigurationLoader(KestrelServerOptions options, IConfiguration configuration)
{
Options = options ?? throw new ArgumentNullException(nameof(options));
Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
}
public KestrelServerOptions Options { get; }
public IConfiguration Configuration { get; }
private IDictionary<string, Action<EndpointConfiguration>> EndpointConfigurations { get; }
= new Dictionary<string, Action<EndpointConfiguration>>(0, StringComparer.OrdinalIgnoreCase);
// Actions that will be delayed until Load so that they aren't applied if the configuration loader is replaced.
private IList<Action> EndpointsToAdd { get; } = new List<Action>();
/// <summary>
/// Specifies a configuration Action to run when an endpoint with the given name is loaded from configuration.
/// </summary>
public KestrelConfigurationLoader Endpoint(string name, Action<EndpointConfiguration> configureOptions)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentNullException(nameof(name));
}
EndpointConfigurations[name] = configureOptions ?? throw new ArgumentNullException(nameof(configureOptions));
return this;
}
/// <summary>
/// Bind to given IP address and port.
/// </summary>
public KestrelConfigurationLoader Endpoint(IPAddress address, int port) => Endpoint(address, port, _ => { });
/// <summary>
/// Bind to given IP address and port.
/// </summary>
public KestrelConfigurationLoader Endpoint(IPAddress address, int port, Action<ListenOptions> configure)
{
if (address == null)
{
throw new ArgumentNullException(nameof(address));
}
return Endpoint(new IPEndPoint(address, port), configure);
}
/// <summary>
/// Bind to given IP endpoint.
/// </summary>
public KestrelConfigurationLoader Endpoint(IPEndPoint endPoint) => Endpoint(endPoint, _ => { });
/// <summary>
/// Bind to given IP address and port.
/// </summary>
public KestrelConfigurationLoader Endpoint(IPEndPoint endPoint, Action<ListenOptions> configure)
{
if (endPoint == null)
{
throw new ArgumentNullException(nameof(endPoint));
}
if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
EndpointsToAdd.Add(() =>
{
Options.Listen(endPoint, configure);
});
return this;
}
/// <summary>
/// Listens on ::1 and 127.0.0.1 with the given port. Requesting a dynamic port by specifying 0 is not supported
/// for this type of endpoint.
/// </summary>
public KestrelConfigurationLoader LocalhostEndpoint(int port) => LocalhostEndpoint(port, options => { });
/// <summary>
/// Listens on ::1 and 127.0.0.1 with the given port. Requesting a dynamic port by specifying 0 is not supported
/// for this type of endpoint.
/// </summary>
public KestrelConfigurationLoader LocalhostEndpoint(int port, Action<ListenOptions> configure)
{
if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
EndpointsToAdd.Add(() =>
{
Options.ListenLocalhost(port, configure);
});
return this;
}
/// <summary>
/// Listens on all IPs using IPv6 [::], or IPv4 0.0.0.0 if IPv6 is not supported.
/// </summary>
public KestrelConfigurationLoader AnyIPEndpoint(int port) => AnyIPEndpoint(port, options => { });
/// <summary>
/// Listens on all IPs using IPv6 [::], or IPv4 0.0.0.0 if IPv6 is not supported.
/// </summary>
public KestrelConfigurationLoader AnyIPEndpoint(int port, Action<ListenOptions> configure)
{
if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
EndpointsToAdd.Add(() =>
{
Options.ListenAnyIP(port, configure);
});
return this;
}
/// <summary>
/// Bind to given Unix domain socket path.
/// </summary>
public KestrelConfigurationLoader UnixSocketEndpoint(string socketPath) => UnixSocketEndpoint(socketPath, _ => { });
/// <summary>
/// Bind to given Unix domain socket path.
/// </summary>
public KestrelConfigurationLoader UnixSocketEndpoint(string socketPath, Action<ListenOptions> configure)
{
if (socketPath == null)
{
throw new ArgumentNullException(nameof(socketPath));
}
if (socketPath.Length == 0 || socketPath[0] != '/')
{
throw new ArgumentException(CoreStrings.UnixSocketPathMustBeAbsolute, nameof(socketPath));
}
if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
EndpointsToAdd.Add(() =>
{
Options.ListenUnixSocket(socketPath, configure);
});
return this;
}
/// <summary>
/// Open a socket file descriptor.
/// </summary>
public KestrelConfigurationLoader HandleEndpoint(ulong handle) => HandleEndpoint(handle, _ => { });
/// <summary>
/// Open a socket file descriptor.
/// </summary>
public KestrelConfigurationLoader HandleEndpoint(ulong handle, Action<ListenOptions> configure)
{
if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
EndpointsToAdd.Add(() =>
{
Options.ListenHandle(handle, configure);
});
return this;
}
public void Load()
{
if (Options.ConfigurationLoader == null)
{
// The loader has already been run.
return;
}
Options.ConfigurationLoader = null;
var configReader = new ConfigurationReader(Configuration);
LoadDefaultCert(configReader);
foreach (var endpoint in configReader.Endpoints)
{
var listenOptions = AddressBinder.ParseAddress(endpoint.Url, out var https);
Options.ApplyEndpointDefaults(listenOptions);
// Compare to UseHttps(httpsOptions => { })
var httpsOptions = new HttpsConnectionAdapterOptions();
if (https)
{
// Defaults
Options.ApplyHttpsDefaults(httpsOptions);
// Specified
httpsOptions.ServerCertificate = LoadCertificate(endpoint.Certificate, endpoint.Name)
?? httpsOptions.ServerCertificate;
}
if (EndpointConfigurations.TryGetValue(endpoint.Name, out var configureEndpoint))
{
var endpointConfig = new EndpointConfiguration(https, listenOptions, httpsOptions, endpoint.ConfigSection);
configureEndpoint(endpointConfig);
}
// EndpointDefaults or configureEndpoint may have added an https adapter.
if (https && !listenOptions.ConnectionAdapters.Any(f => f.IsHttps))
{
if (httpsOptions.ServerCertificate == null)
{
throw new InvalidOperationException(CoreStrings.NoCertSpecifiedNoDevelopmentCertificateFound);
}
listenOptions.UseHttps(httpsOptions);
}
Options.ListenOptions.Add(listenOptions);
}
foreach (var action in EndpointsToAdd)
{
action();
}
}
private void LoadDefaultCert(ConfigurationReader configReader)
{
if (configReader.Certificates.TryGetValue("Default", out var defaultCertConfig))
{
var defaultCert = LoadCertificate(defaultCertConfig, "Default");
if (defaultCert != null)
{
Options.DefaultCertificate = defaultCert;
}
}
}
private X509Certificate2 LoadCertificate(CertificateConfig certInfo, string endpointName)
{
if (certInfo.IsFileCert && certInfo.IsStoreCert)
{
throw new InvalidOperationException(CoreStrings.FormatMultipleCertificateSources(endpointName));
}
else if (certInfo.IsFileCert)
{
var env = Options.ApplicationServices.GetRequiredService<IHostingEnvironment>();
return new X509Certificate2(Path.Combine(env.ContentRootPath, certInfo.Path), certInfo.Password);
}
else if (certInfo.IsStoreCert)
{
return LoadFromStoreCert(certInfo);
}
return null;
}
private static X509Certificate2 LoadFromStoreCert(CertificateConfig certInfo)
{
var subject = certInfo.Subject;
var storeName = certInfo.Store;
var location = certInfo.Location;
var storeLocation = StoreLocation.CurrentUser;
if (!string.IsNullOrEmpty(location))
{
storeLocation = (StoreLocation)Enum.Parse(typeof(StoreLocation), location, ignoreCase: true);
}
var allowInvalid = certInfo.AllowInvalid ?? false;
return CertificateLoader.LoadFromStoreCert(subject, storeName, storeLocation, allowInvalid);
}
}
}

View File

@ -22,7 +22,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
private readonly List<ITransport> _transports = new List<ITransport>();
private readonly Heartbeat _heartbeat;
private readonly IServerAddressesFeature _serverAddresses;
private readonly IDefaultHttpsProvider _defaultHttpsProvider;
private readonly ITransportFactory _transportFactory;
private bool _hasStarted;
@ -34,12 +33,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
{
}
public KestrelServer(IOptions<KestrelServerOptions> options, ITransportFactory transportFactory, ILoggerFactory loggerFactory, IDefaultHttpsProvider defaultHttpsProvider)
: this(transportFactory, CreateServiceContext(options, loggerFactory))
{
_defaultHttpsProvider = defaultHttpsProvider;
}
// For testing
internal KestrelServer(ITransportFactory transportFactory, ServiceContext serviceContext)
{
@ -159,7 +152,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
await transport.BindAsync().ConfigureAwait(false);
}
await AddressBinder.BindAsync(_serverAddresses, Options, Trace, _defaultHttpsProvider, OnBind).ConfigureAwait(false);
await AddressBinder.BindAsync(_serverAddresses, Options, Trace, OnBind).ConfigureAwait(false);
}
catch (Exception ex)
{
@ -224,6 +217,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
private void ValidateOptions()
{
Options.ConfigurationLoader?.Load();
if (Options.Limits.MaxRequestBufferSize.HasValue &&
Options.Limits.MaxRequestBufferSize < Options.Limits.MaxRequestLineSize)
{

View File

@ -4,8 +4,11 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
using Microsoft.Extensions.Configuration;
namespace Microsoft.AspNetCore.Server.Kestrel.Core
{
@ -55,6 +58,78 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
/// </summary>
public KestrelServerLimits Limits { get; } = new KestrelServerLimits();
/// <summary>
/// Provides a configuration source where endpoints will be loaded from on server start.
/// The default is null.
/// </summary>
public KestrelConfigurationLoader ConfigurationLoader { get; set; }
/// <summary>
/// A default configuration action for all endpoints. Use for Listen, configuration, the default url, and URLs.
/// </summary>
private Action<ListenOptions> EndpointDefaults { get; set; } = _ => { };
/// <summary>
/// A default configuration action for all https endpoints.
/// </summary>
private Action<HttpsConnectionAdapterOptions> HttpsDefaults { get; set; } = _ => { };
/// <summary>
/// The default server certificate for https endpoints. This is applied before HttpsDefaults.
/// </summary>
internal X509Certificate2 DefaultCertificate { get; set; }
/// <summary>
/// Specifies a configuration Action to run for each newly created endpoint. Calling this again will replace
/// the prior action.
/// </summary>
public void ConfigureEndpointDefaults(Action<ListenOptions> configureOptions)
{
EndpointDefaults = configureOptions ?? throw new ArgumentNullException(nameof(configureOptions));
}
internal void ApplyEndpointDefaults(ListenOptions listenOptions)
{
listenOptions.KestrelServerOptions = this;
EndpointDefaults(listenOptions);
}
/// <summary>
/// Specifies a configuration Action to run for each newly created https endpoint. Calling this again will replace
/// the prior action.
/// </summary>
public void ConfigureHttpsDefaults(Action<HttpsConnectionAdapterOptions> configureOptions)
{
HttpsDefaults = configureOptions ?? throw new ArgumentNullException(nameof(configureOptions));
}
internal void ApplyHttpsDefaults(HttpsConnectionAdapterOptions httpsOptions)
{
httpsOptions.ServerCertificate = DefaultCertificate;
HttpsDefaults(httpsOptions);
}
/// <summary>
/// Creates a configuration loader for setting up Kestrel.
/// </summary>
public KestrelConfigurationLoader Configure()
{
var loader = new KestrelConfigurationLoader(this, new ConfigurationBuilder().Build());
ConfigurationLoader = loader;
return loader;
}
/// <summary>
/// Creates a configuration loader for setting up Kestrel that takes an IConfiguration as input.
/// This configuration must be scoped to the configuration section for Kestrel.
/// </summary>
public KestrelConfigurationLoader Configure(IConfiguration config)
{
var loader = new KestrelConfigurationLoader(this, config);
ConfigurationLoader = loader;
return loader;
}
/// <summary>
/// Bind to given IP address and port.
/// </summary>
@ -100,13 +175,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
throw new ArgumentNullException(nameof(configure));
}
var listenOptions = new ListenOptions(endPoint) { KestrelServerOptions = this };
var listenOptions = new ListenOptions(endPoint);
ApplyEndpointDefaults(listenOptions);
configure(listenOptions);
ListenOptions.Add(listenOptions);
}
/// <summary>
/// Listens on ::1 and 127.0.0.1 with the given port. Requesting a dynamic port by specifying 0 is not supported
/// for this type of endpoint.
/// </summary>
public void ListenLocalhost(int port) => ListenLocalhost(port, options => { });
/// <summary>
/// Listens on ::1 and 127.0.0.1 with the given port. Requesting a dynamic port by specifying 0 is not supported
/// for this type of endpoint.
/// </summary>
public void ListenLocalhost(int port, Action<ListenOptions> configure)
{
if (configure == null)
@ -114,16 +198,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
throw new ArgumentNullException(nameof(configure));
}
var listenOptions = new LocalhostListenOptions(port)
{
KestrelServerOptions = this,
};
var listenOptions = new LocalhostListenOptions(port);
ApplyEndpointDefaults(listenOptions);
configure(listenOptions);
ListenOptions.Add(listenOptions);
}
/// <summary>
/// Listens on all IPs using IPv6 [::], or IPv4 0.0.0.0 if IPv6 is not supported.
/// </summary>
public void ListenAnyIP(int port) => ListenAnyIP(port, options => { });
/// <summary>
/// Listens on all IPs using IPv6 [::], or IPv4 0.0.0.0 if IPv6 is not supported.
/// </summary>
public void ListenAnyIP(int port, Action<ListenOptions> configure)
{
if (configure == null)
@ -131,10 +219,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
throw new ArgumentNullException(nameof(configure));
}
var listenOptions = new AnyIPListenOptions(port)
{
KestrelServerOptions = this,
};
var listenOptions = new AnyIPListenOptions(port);
ApplyEndpointDefaults(listenOptions);
configure(listenOptions);
ListenOptions.Add(listenOptions);
}
@ -166,7 +252,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
throw new ArgumentNullException(nameof(configure));
}
var listenOptions = new ListenOptions(socketPath) { KestrelServerOptions = this };
var listenOptions = new ListenOptions(socketPath);
ApplyEndpointDefaults(listenOptions);
configure(listenOptions);
ListenOptions.Add(listenOptions);
}
@ -190,7 +277,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
throw new ArgumentNullException(nameof(configure));
}
var listenOptions = new ListenOptions(handle) { KestrelServerOptions = this };
var listenOptions = new ListenOptions(handle);
ApplyEndpointDefaults(listenOptions);
configure(listenOptions);
ListenOptions.Add(listenOptions);
}

View File

@ -1,9 +1,11 @@
// 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.IO;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.AspNetCore.Server.Kestrel.Https.Internal;
using Microsoft.Extensions.DependencyInjection;
@ -16,18 +18,21 @@ namespace Microsoft.AspNetCore.Hosting
/// </summary>
public static class ListenOptionsHttpsExtensions
{
/// <summary>
/// Configure Kestrel to use HTTPS with the default certificate if available.
/// This will throw if no default certificate is configured.
/// </summary>
/// <param name="listenOptions">The <see cref="ListenOptions"/> to configure.</param>
/// <returns>The <see cref="ListenOptions"/>.</returns>
public static ListenOptions UseHttps(this ListenOptions listenOptions) => listenOptions.UseHttps(_ => { });
/// <summary>
/// Configure Kestrel to use HTTPS.
/// </summary>
/// <param name="listenOptions">
/// The <see cref="ListenOptions"/> to configure.
/// </param>
/// <param name="fileName">
/// The name of a certificate file, relative to the directory that contains the application content files.
/// </param>
/// <returns>
/// The <see cref="ListenOptions"/>.
/// </returns>
/// <param name="listenOptions">The <see cref="ListenOptions"/> to configure.</param>
/// <param name="fileName">The name of a certificate file, relative to the directory that contains the application
/// content files.</param>
/// <returns>The <see cref="ListenOptions"/>.</returns>
public static ListenOptions UseHttps(this ListenOptions listenOptions, string fileName)
{
var env = listenOptions.KestrelServerOptions.ApplicationServices.GetRequiredService<IHostingEnvironment>();
@ -37,18 +42,11 @@ namespace Microsoft.AspNetCore.Hosting
/// <summary>
/// Configure Kestrel to use HTTPS.
/// </summary>
/// <param name="listenOptions">
/// The <see cref="ListenOptions"/> to configure.
/// </param>
/// <param name="fileName">
/// The name of a certificate file, relative to the directory that contains the application content files.
/// </param>
/// <param name="password">
/// The password required to access the X.509 certificate data.
/// </param>
/// <returns>
/// The <see cref="ListenOptions"/>.
/// </returns>
/// <param name="listenOptions">The <see cref="ListenOptions"/> to configure.</param>
/// <param name="fileName">The name of a certificate file, relative to the directory that contains the application
/// content files.</param>
/// <param name="password">The password required to access the X.509 certificate data.</param>
/// <returns>The <see cref="ListenOptions"/>.</returns>
public static ListenOptions UseHttps(this ListenOptions listenOptions, string fileName, string password)
{
var env = listenOptions.KestrelServerOptions.ApplicationServices.GetRequiredService<IHostingEnvironment>();
@ -58,32 +56,157 @@ namespace Microsoft.AspNetCore.Hosting
/// <summary>
/// Configure Kestrel to use HTTPS.
/// </summary>
/// <param name="listenOptions">
/// The <see cref="ListenOptions"/> to configure.
/// </param>
/// <param name="serverCertificate">
/// The X.509 certificate.
/// </param>
/// <returns>
/// The <see cref="ListenOptions"/>.
/// </returns>
public static ListenOptions UseHttps(this ListenOptions listenOptions, X509Certificate2 serverCertificate)
/// <param name="listenOptions">The <see cref="ListenOptions"/> to configure.</param>
/// <param name="fileName">The name of a certificate file, relative to the directory that contains the application content files.</param>
/// <param name="password">The password required to access the X.509 certificate data.</param>
/// <param name="configureOptions">An Action to configure the <see cref="HttpsConnectionAdapterOptions"/>.</param>
/// <returns>The <see cref="ListenOptions"/>.</returns>
public static ListenOptions UseHttps(this ListenOptions listenOptions, string fileName, string password,
Action<HttpsConnectionAdapterOptions> configureOptions)
{
return listenOptions.UseHttps(new HttpsConnectionAdapterOptions { ServerCertificate = serverCertificate });
var env = listenOptions.KestrelServerOptions.ApplicationServices.GetRequiredService<IHostingEnvironment>();
return listenOptions.UseHttps(new X509Certificate2(Path.Combine(env.ContentRootPath, fileName), password), configureOptions);
}
/// <summary>
/// Configure Kestrel to use HTTPS.
/// </summary>
/// <param name="listenOptions">
/// The <see cref="ListenOptions"/> to configure.
/// </param>
/// <param name="httpsOptions">
/// Options to configure HTTPS.
/// </param>
/// <returns>
/// The <see cref="ListenOptions"/>.
/// </returns>
/// <param name="listenOptions">The <see cref="ListenOptions"/> to configure.</param>
/// <param name="storeName">The certificate store to load the certificate from.</param>
/// <param name="subject">The subject name for the certificate to load.</param>
/// <returns>The <see cref="ListenOptions"/>.</returns>
public static ListenOptions UseHttps(this ListenOptions listenOptions, StoreName storeName, string subject)
=> listenOptions.UseHttps(storeName, subject, allowInvalid: false);
/// <summary>
/// Configure Kestrel to use HTTPS.
/// </summary>
/// <param name="listenOptions">The <see cref="ListenOptions"/> to configure.</param>
/// <param name="storeName">The certificate store to load the certificate from.</param>
/// <param name="subject">The subject name for the certificate to load.</param>
/// <param name="allowInvalid">Indicates if invalid certificates should be considered, such as self-signed certificates.</param>
/// <returns>The <see cref="ListenOptions"/>.</returns>
public static ListenOptions UseHttps(this ListenOptions listenOptions, StoreName storeName, string subject, bool allowInvalid)
=> listenOptions.UseHttps(storeName, subject, allowInvalid, StoreLocation.CurrentUser);
/// <summary>
/// Configure Kestrel to use HTTPS.
/// </summary>
/// <param name="listenOptions">The <see cref="ListenOptions"/> to configure.</param>
/// <param name="storeName">The certificate store to load the certificate from.</param>
/// <param name="subject">The subject name for the certificate to load.</param>
/// <param name="allowInvalid">Indicates if invalid certificates should be considered, such as self-signed certificates.</param>
/// <param name="location">The store location to load the certificate from.</param>
/// <returns>The <see cref="ListenOptions"/>.</returns>
public static ListenOptions UseHttps(this ListenOptions listenOptions, StoreName storeName, string subject, bool allowInvalid, StoreLocation location)
=> listenOptions.UseHttps(storeName, subject, allowInvalid, location, configureOptions: _ => { });
/// <summary>
/// Configure Kestrel to use HTTPS.
/// </summary>
/// <param name="listenOptions">The <see cref="ListenOptions"/> to configure.</param>
/// <param name="storeName">The certificate store to load the certificate from.</param>
/// <param name="subject">The subject name for the certificate to load.</param>
/// <param name="allowInvalid">Indicates if invalid certificates should be considered, such as self-signed certificates.</param>
/// <param name="location">The store location to load the certificate from.</param>
/// <param name="configureOptions">An Action to configure the <see cref="HttpsConnectionAdapterOptions"/>.</param>
/// <returns>The <see cref="ListenOptions"/>.</returns>
public static ListenOptions UseHttps(this ListenOptions listenOptions, StoreName storeName, string subject, bool allowInvalid, StoreLocation location,
Action<HttpsConnectionAdapterOptions> configureOptions)
{
return listenOptions.UseHttps(CertificateLoader.LoadFromStoreCert(subject, storeName.ToString(), location, allowInvalid), configureOptions);
}
/// <summary>
/// Configure Kestrel to use HTTPS.
/// </summary>
/// <param name="listenOptions"> The <see cref="ListenOptions"/> to configure.</param>
/// <param name="serverCertificate">The X.509 certificate.</param>
/// <returns>The <see cref="ListenOptions"/>.</returns>
public static ListenOptions UseHttps(this ListenOptions listenOptions, X509Certificate2 serverCertificate)
{
if (serverCertificate == null)
{
throw new ArgumentNullException(nameof(serverCertificate));
}
return listenOptions.UseHttps(options =>
{
options.ServerCertificate = serverCertificate;
});
}
/// <summary>
/// Configure Kestrel to use HTTPS.
/// </summary>
/// <param name="listenOptions">The <see cref="ListenOptions"/> to configure.</param>
/// <param name="serverCertificate">The X.509 certificate.</param>
/// <param name="configureOptions">An Action to configure the <see cref="HttpsConnectionAdapterOptions"/>.</param>
/// <returns>The <see cref="ListenOptions"/>.</returns>
public static ListenOptions UseHttps(this ListenOptions listenOptions, X509Certificate2 serverCertificate,
Action<HttpsConnectionAdapterOptions> configureOptions)
{
if (serverCertificate == null)
{
throw new ArgumentNullException(nameof(serverCertificate));
}
if (configureOptions == null)
{
throw new ArgumentNullException(nameof(configureOptions));
}
return listenOptions.UseHttps(options =>
{
options.ServerCertificate = serverCertificate;
configureOptions(options);
});
}
/// <summary>
/// Configure Kestrel to use HTTPS.
/// </summary>
/// <param name="listenOptions">The <see cref="ListenOptions"/> to configure.</param>
/// <param name="configureOptions">An action to configure options for HTTPS.</param>
/// <returns>The <see cref="ListenOptions"/>.</returns>
public static ListenOptions UseHttps(this ListenOptions listenOptions, Action<HttpsConnectionAdapterOptions> configureOptions)
{
if (configureOptions == null)
{
throw new ArgumentNullException(nameof(configureOptions));
}
var options = new HttpsConnectionAdapterOptions();
listenOptions.KestrelServerOptions.ApplyHttpsDefaults(options);
configureOptions(options);
if (options.ServerCertificate == null)
{
throw new InvalidOperationException(CoreStrings.NoCertSpecifiedNoDevelopmentCertificateFound);
}
return listenOptions.UseHttps(options);
}
// Use Https if a default cert is available
internal static bool TryUseHttps(this ListenOptions listenOptions)
{
var options = new HttpsConnectionAdapterOptions();
listenOptions.KestrelServerOptions.ApplyHttpsDefaults(options);
if (options.ServerCertificate == null)
{
return false;
}
listenOptions.UseHttps(options);
return true;
}
/// <summary>
/// Configure Kestrel to use HTTPS.
/// </summary>
/// <param name="listenOptions">The <see cref="ListenOptions"/> to configure.</param>
/// <param name="httpsOptions">Options to configure HTTPS.</param>
/// <returns>The <see cref="ListenOptions"/>.</returns>
public static ListenOptions UseHttps(this ListenOptions listenOptions, HttpsConnectionAdapterOptions httpsOptions)
{
var loggerFactory = listenOptions.KestrelServerOptions.ApplicationServices.GetRequiredService<ILoggerFactory>();

View File

@ -3,6 +3,7 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.Kestrel.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.Kestrel.Core.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -1704,6 +1704,76 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
internal static string FormatServiceCertificateRequired()
=> GetString("ServiceCertificateRequired");
/// <summary>
/// No listening endpoints were configured. Binding to {address0} and {address1} by default.
/// </summary>
internal static string BindingToDefaultAddresses
{
get => GetString("BindingToDefaultAddresses");
}
/// <summary>
/// No listening endpoints were configured. Binding to {address0} and {address1} by default.
/// </summary>
internal static string FormatBindingToDefaultAddresses(object address0, object address1)
=> string.Format(CultureInfo.CurrentCulture, GetString("BindingToDefaultAddresses", "address0", "address1"), address0, address1);
/// <summary>
/// The requested certificate {subject} could not be found in {storeLocation}/{storeName} with AllowInvalid setting: {allowInvalid}.
/// </summary>
internal static string CertNotFoundInStore
{
get => GetString("CertNotFoundInStore");
}
/// <summary>
/// The requested certificate {subject} could not be found in {storeLocation}/{storeName} with AllowInvalid setting: {allowInvalid}.
/// </summary>
internal static string FormatCertNotFoundInStore(object subject, object storeLocation, object storeName, object allowInvalid)
=> string.Format(CultureInfo.CurrentCulture, GetString("CertNotFoundInStore", "subject", "storeLocation", "storeName", "allowInvalid"), subject, storeLocation, storeName, allowInvalid);
/// <summary>
/// The endpoint {endpointName} is missing the required 'Url' parameter.
/// </summary>
internal static string EndpointMissingUrl
{
get => GetString("EndpointMissingUrl");
}
/// <summary>
/// The endpoint {endpointName} is missing the required 'Url' parameter.
/// </summary>
internal static string FormatEndpointMissingUrl(object endpointName)
=> string.Format(CultureInfo.CurrentCulture, GetString("EndpointMissingUrl", "endpointName"), endpointName);
/// <summary>
/// Unable to configure HTTPS endpoint. No server certificate was specified and the default developer certificate could not be found. Try running 'dotnet developercertificates https -t' to setup a developer certificate for use with localhost. For information on configuring HTTPS see https://go.microsoft.com/fwlink/?linkid=848054
/// </summary>
internal static string NoCertSpecifiedNoDevelopmentCertificateFound
{
get => GetString("NoCertSpecifiedNoDevelopmentCertificateFound");
}
/// <summary>
/// Unable to configure HTTPS endpoint. No server certificate was specified and the default developer certificate could not be found. Try running 'dotnet developercertificates https -t' to setup a developer certificate for use with localhost. For information on configuring HTTPS see https://go.microsoft.com/fwlink/?linkid=848054
/// </summary>
internal static string FormatNoCertSpecifiedNoDevelopmentCertificateFound()
=> GetString("NoCertSpecifiedNoDevelopmentCertificateFound");
/// <summary>
/// The endpoint {endpointName} specified multiple certificate sources.
/// </summary>
internal static string MultipleCertificateSources
{
get => GetString("MultipleCertificateSources");
}
/// <summary>
/// The endpoint {endpointName} specified multiple certificate sources.
/// </summary>
internal static string FormatMultipleCertificateSources(object endpointName)
=> string.Format(CultureInfo.CurrentCulture, GetString("MultipleCertificateSources", "endpointName"), endpointName);
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -1,42 +0,0 @@
// 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.Linq;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Certificates.Generation;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal
{
public class DefaultHttpsProvider : IDefaultHttpsProvider
{
private static readonly CertificateManager _certificateManager = new CertificateManager();
private readonly ILogger<DefaultHttpsProvider> _logger;
public DefaultHttpsProvider(ILogger<DefaultHttpsProvider> logger)
{
_logger = logger;
}
public void ConfigureHttps(ListenOptions listenOptions)
{
var certificate = _certificateManager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: true)
.FirstOrDefault();
if (certificate != null)
{
_logger.LocatedDevelopmentCertificate(certificate);
listenOptions.UseHttps(certificate);
}
else
{
_logger.UnableToLocateDevelopmentCertificate();
throw new InvalidOperationException(KestrelStrings.HttpsUrlProvidedButNoDevelopmentCertificateFound);
}
}
}
}

View File

@ -12,8 +12,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="$(MicrosoftAspNetCoreHostingPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Certificates.Generation.Sources" PrivateAssets="All" Version="$(MicrosoftAspNetCoreCertificatesGenerationSourcesPackageVersion)" />
<PackageReference Include="System.Security.Cryptography.Cng" Version="$(SystemSecurityCryptographyCngPackageVersion)" />
</ItemGroup>
<ItemGroup>

View File

@ -1,123 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="HttpsUrlProvidedButNoDevelopmentCertificateFound" xml:space="preserve">
<value>Unable to configure HTTPS endpoint. Try running 'dotnet developercertificates https -t' to setup a developer certificate for use with localhost. For information on configuring HTTPS see https://go.microsoft.com/fwlink/?linkid=848054</value>
</data>
</root>

View File

@ -1,44 +0,0 @@
// <auto-generated />
namespace Microsoft.AspNetCore.Server.Kestrel
{
using System.Globalization;
using System.Reflection;
using System.Resources;
internal static class KestrelStrings
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.AspNetCore.Server.Kestrel.KestrelStrings", typeof(KestrelStrings).GetTypeInfo().Assembly);
/// <summary>
/// Unable to configure HTTPS endpoint. Try running 'dotnet developercertificates https -t' to setup a developer certificate for use with localhost. For information on configuring HTTPS see https://go.microsoft.com/fwlink/?linkid=848054
/// </summary>
internal static string HttpsUrlProvidedButNoDevelopmentCertificateFound
{
get => GetString("HttpsUrlProvidedButNoDevelopmentCertificateFound");
}
/// <summary>
/// Unable to configure HTTPS endpoint. Try running 'dotnet developercertificates https -t' to setup a developer certificate for use with localhost. For information on configuring HTTPS see https://go.microsoft.com/fwlink/?linkid=848054
/// </summary>
internal static string FormatHttpsUrlProvidedButNoDevelopmentCertificateFound()
=> GetString("HttpsUrlProvidedButNoDevelopmentCertificateFound");
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
System.Diagnostics.Debug.Assert(value != null);
if (formatterNames != null)
{
for (var i = 0; i < formatterNames.Length; i++)
{
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
}
}
return value;
}
}
}

View File

@ -34,7 +34,6 @@ namespace Microsoft.AspNetCore.Hosting
services.AddTransient<IConfigureOptions<KestrelServerOptions>, KestrelServerOptionsSetup>();
services.AddSingleton<IServer, KestrelServer>();
services.AddSingleton<IDefaultHttpsProvider, DefaultHttpsProvider>();
});
}

View File

@ -54,46 +54,50 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
public void ParseAddressDefaultsToAnyIPOnInvalidIPAddress(string host)
{
var options = new KestrelServerOptions();
var listenOptions = AddressBinder.ParseAddress($"http://{host}", options, Mock.Of<IDefaultHttpsProvider>());
var listenOptions = AddressBinder.ParseAddress($"http://{host}", out var https);
Assert.IsType<AnyIPListenOptions>(listenOptions);
Assert.Equal(ListenType.IPEndPoint, listenOptions.Type);
Assert.Equal(IPAddress.IPv6Any, listenOptions.IPEndPoint.Address);
Assert.Equal(80, listenOptions.IPEndPoint.Port);
Assert.False(https);
}
[Fact]
public void ParseAddressLocalhost()
{
var options = new KestrelServerOptions();
var listenOptions = AddressBinder.ParseAddress("http://localhost", options, Mock.Of<IDefaultHttpsProvider>());
var listenOptions = AddressBinder.ParseAddress("http://localhost", out var https);
Assert.IsType<LocalhostListenOptions>(listenOptions);
Assert.Equal(ListenType.IPEndPoint, listenOptions.Type);
Assert.Equal(IPAddress.Loopback, listenOptions.IPEndPoint.Address);
Assert.Equal(80, listenOptions.IPEndPoint.Port);
Assert.False(https);
}
[Fact]
public void ParseAddressUnixPipe()
{
var options = new KestrelServerOptions();
var listenOptions = AddressBinder.ParseAddress("http://unix:/tmp/kestrel-test.sock", options, Mock.Of<IDefaultHttpsProvider>());
var listenOptions = AddressBinder.ParseAddress("http://unix:/tmp/kestrel-test.sock", out var https);
Assert.Equal(ListenType.SocketPath, listenOptions.Type);
Assert.Equal("/tmp/kestrel-test.sock", listenOptions.SocketPath);
Assert.False(https);
}
[Theory]
[InlineData("http://10.10.10.10:5000/", "10.10.10.10", 5000)]
[InlineData("http://[::1]:5000", "::1", 5000)]
[InlineData("http://[::1]", "::1", 80)]
[InlineData("http://127.0.0.1", "127.0.0.1", 80)]
[InlineData("https://127.0.0.1", "127.0.0.1", 443)]
public void ParseAddressIP(string address, string ip, int port)
[InlineData("http://10.10.10.10:5000/", "10.10.10.10", 5000, false)]
[InlineData("http://[::1]:5000", "::1", 5000, false)]
[InlineData("http://[::1]", "::1", 80, false)]
[InlineData("http://127.0.0.1", "127.0.0.1", 80, false)]
[InlineData("https://127.0.0.1", "127.0.0.1", 443, true)]
public void ParseAddressIP(string address, string ip, int port, bool isHttps)
{
var options = new KestrelServerOptions();
var listenOptions = AddressBinder.ParseAddress(address, options, Mock.Of<IDefaultHttpsProvider>());
var listenOptions = AddressBinder.ParseAddress(address, out var https);
Assert.Equal(ListenType.IPEndPoint, listenOptions.Type);
Assert.Equal(IPAddress.Parse(ip), listenOptions.IPEndPoint.Address);
Assert.Equal(port, listenOptions.IPEndPoint.Port);
Assert.Equal(isHttps, https);
}
[Fact]
@ -107,7 +111,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
AddressBinder.BindAsync(addresses,
options,
NullLogger.Instance,
Mock.Of<IDefaultHttpsProvider>(),
endpoint => throw new AddressInUseException("already in use")));
}
@ -128,7 +131,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await AddressBinder.BindAsync(addresses,
options,
logger,
Mock.Of<IDefaultHttpsProvider>(),
endpoint =>
{
if (endpoint.IPEndPoint.Address == IPAddress.IPv6Any)

View File

@ -17,6 +17,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(MicrosoftExtensionsDependencyInjectionPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Testing" Version="$(MicrosoftAspNetCoreTestingPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="$(MicrosoftAspNetCoreHttpPackageVersion)" />

View File

@ -29,5 +29,40 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.True(options.AllowSynchronousIO);
}
[Fact]
public void ConfigureEndpointDefaultsAppliesToNewEndpoints()
{
var options = new KestrelServerOptions();
options.ListenLocalhost(5000);
Assert.True(options.ListenOptions[0].NoDelay);
options.ConfigureEndpointDefaults(opt =>
{
opt.NoDelay = false;
});
options.Listen(new IPEndPoint(IPAddress.Loopback, 5000), opt =>
{
// ConfigureEndpointDefaults runs before this callback
Assert.False(opt.NoDelay);
});
Assert.False(options.ListenOptions[1].NoDelay);
options.ListenLocalhost(5000, opt =>
{
Assert.False(opt.NoDelay);
opt.NoDelay = true; // Can be overriden
});
Assert.True(options.ListenOptions[2].NoDelay);
options.ListenAnyIP(5000, opt =>
{
Assert.False(opt.NoDelay);
});
Assert.False(options.ListenOptions[3].NoDelay);
}
}
}

View File

@ -9,9 +9,9 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq;
@ -21,12 +21,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
public class KestrelServerTests
{
private KestrelServerOptions CreateServerOptions()
{
var serverOptions = new KestrelServerOptions();
serverOptions.ApplicationServices = new ServiceCollection()
.AddLogging()
.BuildServiceProvider();
return serverOptions;
}
[Fact]
public void StartWithInvalidAddressThrows()
{
var testLogger = new TestApplicationErrorLogger { ThrowOnCriticalErrors = false };
using (var server = CreateServer(new KestrelServerOptions(), testLogger))
using (var server = CreateServer(CreateServerOptions(), testLogger))
{
server.Features.Get<IServerAddressesFeature>().Addresses.Add("http:/asdf");
@ -40,34 +49,35 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact]
public void StartWithHttpsAddressConfiguresHttpsEndpoints()
{
var mockDefaultHttpsProvider = new Mock<IDefaultHttpsProvider>();
using (var server = CreateServer(new KestrelServerOptions(), mockDefaultHttpsProvider.Object))
var options = CreateServerOptions();
options.DefaultCertificate = TestResources.GetTestCertificate();
using (var server = CreateServer(options))
{
server.Features.Get<IServerAddressesFeature>().Addresses.Add("https://127.0.0.1:0");
StartDummyApplication(server);
mockDefaultHttpsProvider.Verify(provider => provider.ConfigureHttps(It.IsAny<ListenOptions>()), Times.Once);
Assert.True(server.Options.ListenOptions.Any());
Assert.Contains(server.Options.ListenOptions[0].ConnectionAdapters, adapter => adapter.IsHttps);
}
}
[Fact]
public void KestrelServerThrowsUsefulExceptionIfDefaultHttpsProviderNotAdded()
{
using (var server = CreateServer(new KestrelServerOptions(), defaultHttpsProvider: null, throwOnCriticalErrors: false))
using (var server = CreateServer(CreateServerOptions(), throwOnCriticalErrors: false))
{
server.Features.Get<IServerAddressesFeature>().Addresses.Add("https://127.0.0.1:0");
var ex = Assert.Throws<InvalidOperationException>(() => StartDummyApplication(server));
Assert.Equal(CoreStrings.UnableToConfigureHttpsBindings, ex.Message);
Assert.Equal(CoreStrings.NoCertSpecifiedNoDevelopmentCertificateFound, ex.Message);
}
}
[Fact]
public void KestrelServerDoesNotThrowIfNoDefaultHttpsProviderButNoHttpUrls()
{
using (var server = CreateServer(new KestrelServerOptions(), defaultHttpsProvider: null))
using (var server = CreateServer(CreateServerOptions()))
{
server.Features.Get<IServerAddressesFeature>().Addresses.Add("http://127.0.0.1:0");
@ -78,12 +88,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact]
public void KestrelServerDoesNotThrowIfNoDefaultHttpsProviderButManualListenOptions()
{
var mockDefaultHttpsProvider = new Mock<IDefaultHttpsProvider>();
var serverOptions = new KestrelServerOptions();
var serverOptions = CreateServerOptions();
serverOptions.Listen(new IPEndPoint(IPAddress.Loopback, 0));
using (var server = CreateServer(serverOptions, defaultHttpsProvider: null))
using (var server = CreateServer(serverOptions))
{
server.Features.Get<IServerAddressesFeature>().Addresses.Add("https://127.0.0.1:0");
@ -322,9 +330,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
return new KestrelServer(Options.Create(options), new MockTransportFactory(), new LoggerFactory(new[] { new KestrelTestLoggerProvider(testLogger) }));
}
private static KestrelServer CreateServer(KestrelServerOptions options, IDefaultHttpsProvider defaultHttpsProvider, bool throwOnCriticalErrors = true)
private static KestrelServer CreateServer(KestrelServerOptions options, bool throwOnCriticalErrors = true)
{
return new KestrelServer(Options.Create(options), new MockTransportFactory(), new LoggerFactory(new[] { new KestrelTestLoggerProvider(throwOnCriticalErrors) }), defaultHttpsProvider);
return new KestrelServer(Options.Create(options), new MockTransportFactory(), new LoggerFactory(new[] { new KestrelTestLoggerProvider(throwOnCriticalErrors) }));
}
private static void StartDummyApplication(IServer server)

View File

@ -8,6 +8,7 @@ using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
@ -17,6 +18,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.AspNetCore.Testing;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.Logging;
@ -189,6 +191,40 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
private Task RegisterAddresses_StaticPort_Success(string addressInput, string[] testUrls) =>
RunTestWithStaticPort(port => RegisterAddresses_Success($"{addressInput}:{port}", testUrls, port));
[Fact]
public async Task RegisterHttpAddress_UpradedToHttpsByConfigureEndpointDefaults()
{
var hostBuilder = TransportSelector.GetWebHostBuilder()
.UseKestrel(serverOptions =>
{
serverOptions.ConfigureEndpointDefaults(listenOptions =>
{
listenOptions.UseHttps(TestResources.GetTestCertificate());
});
})
.ConfigureLogging(_configureLoggingDelegate)
.UseUrls("http://127.0.0.1:0")
.Configure(app =>
{
var serverAddresses = app.ServerFeatures.Get<IServerAddressesFeature>();
app.Run(context =>
{
Assert.Single(serverAddresses.Addresses);
return context.Response.WriteAsync(serverAddresses.Addresses.First());
});
});
using (var host = hostBuilder.Build())
{
host.Start();
var expectedUrl = $"https://127.0.0.1:{host.GetPort()}";
var response = await HttpClientSlim.GetStringAsync(expectedUrl, validateCertificate: false);
Assert.Equal(expectedUrl, response);
}
}
private async Task RunTestWithStaticPort(Func<int, Task> test)
{
var retryCount = 0;
@ -361,13 +397,40 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
return RegisterDefaultServerAddresses_Success(new[] { "http://127.0.0.1:5000", "http://[::1]:5000" });
}
private async Task RegisterDefaultServerAddresses_Success(IEnumerable<string> addresses)
[ConditionalFact]
[PortSupportedCondition(5000)]
[PortSupportedCondition(5001)]
public Task DefaultsServerAddress_BindsToIPv4WithHttps()
{
return RegisterDefaultServerAddresses_Success(
new[] { "http://127.0.0.1:5000", "https://127.0.0.1:5001" }, mockHttps: true);
}
[ConditionalFact]
[IPv6SupportedCondition]
[PortSupportedCondition(5000)]
[PortSupportedCondition(5001)]
public Task DefaultsServerAddress_BindsToIPv6WithHttps()
{
return RegisterDefaultServerAddresses_Success(new[] {
"http://127.0.0.1:5000", "http://[::1]:5000",
"https://127.0.0.1:5001", "https://[::1]:5001"},
mockHttps: true);
}
private async Task RegisterDefaultServerAddresses_Success(IEnumerable<string> addresses, bool mockHttps = false)
{
var testLogger = new TestApplicationErrorLogger();
var hostBuilder = TransportSelector.GetWebHostBuilder()
.ConfigureLogging(_configureLoggingDelegate)
.UseKestrel()
.UseKestrel(options =>
{
if (mockHttps)
{
options.DefaultCertificate = new X509Certificate2(TestResources.TestCertificatePath, "testPassword");
}
})
.ConfigureLogging(builder => builder
.AddProvider(new KestrelTestLoggerProvider(testLogger))
.SetMinimumLevel(LogLevel.Debug))
@ -378,13 +441,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
host.Start();
Assert.Equal(5000, host.GetPort());
if (mockHttps)
{
Assert.Contains(5001, host.GetPorts());
}
Assert.Single(testLogger.Messages, log => log.LogLevel == LogLevel.Debug &&
string.Equals(CoreStrings.FormatBindingToDefaultAddress(Constants.DefaultServerAddress),
log.Message, StringComparison.Ordinal));
(string.Equals(CoreStrings.FormatBindingToDefaultAddresses(Constants.DefaultServerAddress, Constants.DefaultServerHttpsAddress), log.Message, StringComparison.Ordinal)
|| string.Equals(CoreStrings.FormatBindingToDefaultAddress(Constants.DefaultServerAddress), log.Message, StringComparison.Ordinal)));
foreach (var address in addresses)
{
Assert.Equal(new Uri(address).ToString(), await HttpClientSlim.GetStringAsync(address));
Assert.Equal(new Uri(address).ToString(), await HttpClientSlim.GetStringAsync(address, validateCertificate: false));
}
}
}
@ -933,7 +1002,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
}
}
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
private class PortSupportedConditionAttribute : Attribute, ITestCondition
{
private readonly int _port;

View File

@ -0,0 +1,64 @@
// 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.Linq;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Server.Kestrel.Https.Internal;
using Microsoft.AspNetCore.Testing;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
public class CertificateLoaderTests
{
private readonly ITestOutputHelper _output;
public CertificateLoaderTests(ITestOutputHelper output)
{
_output = output;
}
[Theory]
[InlineData("no_extensions.pfx")]
public void IsCertificateAllowedForServerAuth_AllowWithNoExtensions(string testCertName)
{
var certPath = TestResources.GetCertPath(testCertName);
_output.WriteLine("Loading " + certPath);
var cert = new X509Certificate2(certPath, "testPassword");
Assert.Empty(cert.Extensions.OfType<X509EnhancedKeyUsageExtension>());
Assert.True(CertificateLoader.IsCertificateAllowedForServerAuth(cert));
}
[Theory]
[InlineData("eku.server.pfx")]
[InlineData("eku.multiple_usages.pfx")]
public void IsCertificateAllowedForServerAuth_ValidatesEnhancedKeyUsageOnCertificate(string testCertName)
{
var certPath = TestResources.GetCertPath(testCertName);
_output.WriteLine("Loading " + certPath);
var cert = new X509Certificate2(certPath, "testPassword");
Assert.NotEmpty(cert.Extensions);
var eku = Assert.Single(cert.Extensions.OfType<X509EnhancedKeyUsageExtension>());
Assert.NotEmpty(eku.EnhancedKeyUsages);
Assert.True(CertificateLoader.IsCertificateAllowedForServerAuth(cert));
}
[Theory]
[InlineData("eku.code_signing.pfx")]
[InlineData("eku.client.pfx")]
public void IsCertificateAllowedForServerAuth_RejectsCertificatesMissingServerEku(string testCertName)
{
var certPath = TestResources.GetCertPath(testCertName);
_output.WriteLine("Loading " + certPath);
var cert = new X509Certificate2(certPath, "testPassword");
Assert.NotEmpty(cert.Extensions);
var eku = Assert.Single(cert.Extensions.OfType<X509EnhancedKeyUsageExtension>());
Assert.NotEmpty(eku.EnhancedKeyUsages);
Assert.False(CertificateLoader.IsCertificateAllowedForServerAuth(cert));
}
}
}

View File

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
@ -14,8 +13,11 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.AspNetCore.Server.Kestrel.Https.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Internal;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@ -26,6 +28,61 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
public class HttpsTests
{
private KestrelServerOptions CreateServerOptions()
{
var serverOptions = new KestrelServerOptions();
serverOptions.ApplicationServices = new ServiceCollection()
.AddLogging()
.BuildServiceProvider();
return serverOptions;
}
[Fact]
public void UseHttpsDefaultsToDefaultCert()
{
var serverOptions = CreateServerOptions();
var defaultCert = new X509Certificate2(TestResources.TestCertificatePath, "testPassword");
serverOptions.DefaultCertificate = defaultCert;
serverOptions.ListenLocalhost(5000, options =>
{
options.UseHttps();
});
serverOptions.ListenLocalhost(5001, options =>
{
options.UseHttps(opt =>
{
Assert.Equal(defaultCert, opt.ServerCertificate);
});
});
}
[Fact]
public void ConfigureHttpsDefaultsOverridesDefaultCert()
{
var serverOptions = CreateServerOptions();
var defaultCert = new X509Certificate2(TestResources.TestCertificatePath, "testPassword");
serverOptions.DefaultCertificate = defaultCert;
serverOptions.ConfigureHttpsDefaults(options =>
{
Assert.Equal(defaultCert, options.ServerCertificate);
options.ServerCertificate = null;
options.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
});
serverOptions.ListenLocalhost(5000, options =>
{
options.UseHttps(opt =>
{
Assert.Null(opt.ServerCertificate);
Assert.Equal(ClientCertificateMode.RequireCertificate, opt.ClientCertificateMode);
// So UseHttps won't throw
opt.ServerCertificate = defaultCert;
});
});
}
[Fact]
public async Task EmptyRequestLoggedAsInformation()
{
@ -270,10 +327,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
{
options.Listen(new IPEndPoint(IPAddress.Loopback, 0), listenOptions =>
{
listenOptions.UseHttps(new HttpsConnectionAdapterOptions
listenOptions.UseHttps(o =>
{
ServerCertificate = new X509Certificate2(TestResources.TestCertificatePath, "testPassword"),
HandshakeTimeout = TimeSpan.FromSeconds(1)
o.ServerCertificate = new X509Certificate2(TestResources.TestCertificatePath, "testPassword");
o.HandshakeTimeout = TimeSpan.FromSeconds(1);
});
});
})

View File

@ -0,0 +1,177 @@
// 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 Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.Extensions.Configuration;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.Tests
{
public class ConfigurationReaderTests
{
[Fact]
public void ReadCertificatesWhenNoCertificatsSection_ReturnsEmptyCollection()
{
var config = new ConfigurationBuilder().AddInMemoryCollection().Build();
var reader = new ConfigurationReader(config);
var certificates = reader.Certificates;
Assert.NotNull(certificates);
Assert.False(certificates.Any());
}
[Fact]
public void ReadCertificatesWhenEmptyCertificatsSection_ReturnsEmptyCollection()
{
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Certificates", ""),
}).Build();
var reader = new ConfigurationReader(config);
var certificates = reader.Certificates;
Assert.NotNull(certificates);
Assert.False(certificates.Any());
}
[Fact]
public void ReadCertificatsSection_ReturnsCollection()
{
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Certificates:FileCert:Path", "/path/cert.pfx"),
new KeyValuePair<string, string>("Certificates:FileCert:Password", "certpassword"),
new KeyValuePair<string, string>("Certificates:StoreCert:Subject", "certsubject"),
new KeyValuePair<string, string>("Certificates:StoreCert:Store", "certstore"),
new KeyValuePair<string, string>("Certificates:StoreCert:Location", "cetlocation"),
new KeyValuePair<string, string>("Certificates:StoreCert:AllowInvalid", "true"),
}).Build();
var reader = new ConfigurationReader(config);
var certificates = reader.Certificates;
Assert.NotNull(certificates);
Assert.Equal(2, certificates.Count);
var fileCert = certificates["FileCert"];
Assert.True(fileCert.IsFileCert);
Assert.False(fileCert.IsStoreCert);
Assert.Equal("/path/cert.pfx", fileCert.Path);
Assert.Equal("certpassword", fileCert.Password);
var storeCert = certificates["StoreCert"];
Assert.False(storeCert.IsFileCert);
Assert.True(storeCert.IsStoreCert);
Assert.Equal("certsubject", storeCert.Subject);
Assert.Equal("certstore", storeCert.Store);
Assert.Equal("cetlocation", storeCert.Location);
Assert.True(storeCert.AllowInvalid);
}
[Fact]
public void ReadEndpointsWhenNoEndpointsSection_ReturnsEmptyCollection()
{
var config = new ConfigurationBuilder().AddInMemoryCollection().Build();
var reader = new ConfigurationReader(config);
var endpoints = reader.Endpoints;
Assert.NotNull(endpoints);
Assert.False(endpoints.Any());
}
[Fact]
public void ReadEndpointsWhenEmptyEndpointsSection_ReturnsEmptyCollection()
{
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Endpoints", ""),
}).Build();
var reader = new ConfigurationReader(config);
var endpoints = reader.Endpoints;
Assert.NotNull(endpoints);
Assert.False(endpoints.Any());
}
[Fact]
public void ReadEndpointWithMissingUrl_Throws()
{
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Endpoints:End1", ""),
}).Build();
var reader = new ConfigurationReader(config);
Assert.Throws<InvalidOperationException>(() => reader.Endpoints);
}
[Fact]
public void ReadEndpointWithEmptyUrl_Throws()
{
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Endpoints:End1:Url", ""),
}).Build();
var reader = new ConfigurationReader(config);
Assert.Throws<InvalidOperationException>(() => reader.Endpoints);
}
[Fact]
public void ReadEndpointsSection_ReturnsCollection()
{
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Endpoints:End1:Url", "http://*:5001"),
new KeyValuePair<string, string>("Endpoints:End2:Url", "https://*:5002"),
new KeyValuePair<string, string>("Endpoints:End3:Url", "https://*:5003"),
new KeyValuePair<string, string>("Endpoints:End3:Certificate:Path", "/path/cert.pfx"),
new KeyValuePair<string, string>("Endpoints:End3:Certificate:Password", "certpassword"),
new KeyValuePair<string, string>("Endpoints:End4:Url", "https://*:5004"),
new KeyValuePair<string, string>("Endpoints:End4:Certificate:Subject", "certsubject"),
new KeyValuePair<string, string>("Endpoints:End4:Certificate:Store", "certstore"),
new KeyValuePair<string, string>("Endpoints:End4:Certificate:Location", "cetlocation"),
new KeyValuePair<string, string>("Endpoints:End4:Certificate:AllowInvalid", "true"),
}).Build();
var reader = new ConfigurationReader(config);
var endpoints = reader.Endpoints;
Assert.NotNull(endpoints);
Assert.Equal(4, endpoints.Count());
var end1 = endpoints.First();
Assert.Equal("End1", end1.Name);
Assert.Equal("http://*:5001", end1.Url);
Assert.NotNull(end1.ConfigSection);
Assert.NotNull(end1.Certificate);
Assert.False(end1.Certificate.ConfigSection.Exists());
var end2 = endpoints.Skip(1).First();
Assert.Equal("End2", end2.Name);
Assert.Equal("https://*:5002", end2.Url);
Assert.NotNull(end2.ConfigSection);
Assert.NotNull(end2.Certificate);
Assert.False(end2.Certificate.ConfigSection.Exists());
var end3 = endpoints.Skip(2).First();
Assert.Equal("End3", end3.Name);
Assert.Equal("https://*:5003", end3.Url);
Assert.NotNull(end3.ConfigSection);
Assert.NotNull(end3.Certificate);
Assert.True(end3.Certificate.ConfigSection.Exists());
var cert3 = end3.Certificate;
Assert.True(cert3.IsFileCert);
Assert.False(cert3.IsStoreCert);
Assert.Equal("/path/cert.pfx", cert3.Path);
Assert.Equal("certpassword", cert3.Password);
var end4 = endpoints.Skip(3).First();
Assert.Equal("End4", end4.Name);
Assert.Equal("https://*:5004", end4.Url);
Assert.NotNull(end4.ConfigSection);
Assert.NotNull(end4.Certificate);
Assert.True(end4.Certificate.ConfigSection.Exists());
var cert4 = end4.Certificate;
Assert.False(cert4.IsFileCert);
Assert.True(cert4.IsStoreCert);
Assert.Equal("certsubject", cert4.Subject);
Assert.Equal("certstore", cert4.Store);
Assert.Equal("cetlocation", cert4.Location);
Assert.True(cert4.AllowInvalid);
}
}
}

View File

@ -6,8 +6,14 @@
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\shared\**\*.cs" />
<Content Include="..\shared\TestCertificates\*.pfx" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Kestrel\Kestrel.csproj" />
<PackageReference Include="Microsoft.AspNetCore.Testing" Version="$(MicrosoftAspNetCoreTestingPackageVersion)" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,213 @@
// 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.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.Tests
{
public class KestrelConfigurationBuilderTests
{
private KestrelServerOptions CreateServerOptions()
{
var serverOptions = new KestrelServerOptions();
serverOptions.ApplicationServices = new ServiceCollection()
.AddLogging()
.BuildServiceProvider();
return serverOptions;
}
[Fact]
public void ConfigureNamedEndpoint_OnlyRunForMatchingConfig()
{
var found = false;
var serverOptions = CreateServerOptions();
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Endpoints:Found:Url", "http://*:5001"),
}).Build();
serverOptions.Configure(config)
.Endpoint("Found", endpointOptions => found = true)
.Endpoint("NotFound", endpointOptions => throw new NotImplementedException())
.Load();
Assert.Single(serverOptions.ListenOptions);
Assert.Equal(5001, serverOptions.ListenOptions[0].IPEndPoint.Port);
Assert.True(found);
}
[Fact]
public void ConfigureEndpoint_OnlyRunWhenBuildIsCalled()
{
var run = false;
var serverOptions = CreateServerOptions();
serverOptions.Configure()
.LocalhostEndpoint(5001, endpointOptions => run = true);
Assert.Empty(serverOptions.ListenOptions);
serverOptions.ConfigurationLoader.Load();
Assert.Single(serverOptions.ListenOptions);
Assert.Equal(5001, serverOptions.ListenOptions[0].IPEndPoint.Port);
Assert.True(run);
}
[Fact]
public void CallBuildTwice_OnlyRunsOnce()
{
var serverOptions = CreateServerOptions();
var builder = serverOptions.Configure()
.LocalhostEndpoint(5001);
Assert.Empty(serverOptions.ListenOptions);
Assert.Equal(builder, serverOptions.ConfigurationLoader);
builder.Load();
Assert.Single(serverOptions.ListenOptions);
Assert.Equal(5001, serverOptions.ListenOptions[0].IPEndPoint.Port);
Assert.Null(serverOptions.ConfigurationLoader);
builder.Load();
Assert.Single(serverOptions.ListenOptions);
Assert.Equal(5001, serverOptions.ListenOptions[0].IPEndPoint.Port);
Assert.Null(serverOptions.ConfigurationLoader);
}
[Fact]
public void Configure_IsReplacable()
{
var run1 = false;
var serverOptions = CreateServerOptions();
var config1 = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Endpoints:End1:Url", "http://*:5001"),
}).Build();
serverOptions.Configure(config1)
.LocalhostEndpoint(5001, endpointOptions => run1 = true);
Assert.Empty(serverOptions.ListenOptions);
Assert.False(run1);
var run2 = false;
var config2 = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Endpoints:End2:Url", "http://*:5002"),
}).Build();
serverOptions.Configure(config2)
.LocalhostEndpoint(5003, endpointOptions => run2 = true);
serverOptions.ConfigurationLoader.Load();
Assert.Equal(2, serverOptions.ListenOptions.Count);
Assert.Equal(5002, serverOptions.ListenOptions[0].IPEndPoint.Port);
Assert.Equal(5003, serverOptions.ListenOptions[1].IPEndPoint.Port);
Assert.False(run1);
Assert.True(run2);
}
[Fact]
public void ConfigureDefaultsAppliesToNewConfigureEndpoints()
{
var serverOptions = CreateServerOptions();
serverOptions.ConfigureEndpointDefaults(opt =>
{
opt.NoDelay = false;
});
serverOptions.ConfigureHttpsDefaults(opt =>
{
opt.ServerCertificate = new X509Certificate2(TestResources.TestCertificatePath, "testPassword");
opt.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
});
var ran1 = false;
var ran2 = false;
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Endpoints:End1:Url", "https://*:5001"),
}).Build();
serverOptions.Configure(config)
.Endpoint("End1", opt =>
{
ran1 = true;
Assert.True(opt.IsHttps);
Assert.NotNull(opt.HttpsOptions.ServerCertificate);
Assert.Equal(ClientCertificateMode.RequireCertificate, opt.HttpsOptions.ClientCertificateMode);
Assert.False(opt.ListenOptions.NoDelay);
})
.LocalhostEndpoint(5002, opt =>
{
ran2 = true;
Assert.False(opt.NoDelay);
})
.Load();
Assert.True(ran1);
Assert.True(ran2);
Assert.NotNull(serverOptions.ListenOptions[0].ConnectionAdapters.Where(adapter => adapter.IsHttps).SingleOrDefault());
Assert.Null(serverOptions.ListenOptions[1].ConnectionAdapters.Where(adapter => adapter.IsHttps).SingleOrDefault());
}
[Fact]
public void ConfigureEndpointDefaultCanEnableHttps()
{
var serverOptions = CreateServerOptions();
serverOptions.ConfigureEndpointDefaults(opt =>
{
opt.NoDelay = false;
opt.UseHttps(new X509Certificate2(TestResources.TestCertificatePath, "testPassword"));
});
serverOptions.ConfigureHttpsDefaults(opt =>
{
opt.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
});
var ran1 = false;
var ran2 = false;
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Endpoints:End1:Url", "https://*:5001"),
}).Build();
serverOptions.Configure(config)
.Endpoint("End1", opt =>
{
ran1 = true;
Assert.True(opt.IsHttps);
Assert.Equal(ClientCertificateMode.RequireCertificate, opt.HttpsOptions.ClientCertificateMode);
Assert.False(opt.ListenOptions.NoDelay);
})
.LocalhostEndpoint(5002, opt =>
{
ran2 = true;
Assert.False(opt.NoDelay);
})
.Load();
Assert.True(ran1);
Assert.True(ran2);
// You only get Https once per endpoint.
Assert.NotNull(serverOptions.ListenOptions[0].ConnectionAdapters.Where(adapter => adapter.IsHttps).SingleOrDefault());
Assert.NotNull(serverOptions.ListenOptions[1].ConnectionAdapters.Where(adapter => adapter.IsHttps).SingleOrDefault());
}
}
}

View File

@ -3,10 +3,10 @@
set -e
cd /publish
systemd-socket-activate -l 8080 -E ASPNETCORE_BASE_PORT=7000 dotnet SampleApp.dll &
systemd-socket-activate -l 8080 -E ASPNETCORE_BASE_PORT=7000 dotnet SystemdTestApp.dll &
socat TCP-LISTEN:8081,fork TCP-CONNECT:127.0.0.1:7000 &
socat TCP-LISTEN:8082,fork TCP-CONNECT:127.0.0.1:7001 &
systemd-socket-activate -l /tmp/activate-kestrel.sock -E ASPNETCORE_BASE_PORT=7100 dotnet SampleApp.dll &
systemd-socket-activate -l /tmp/activate-kestrel.sock -E ASPNETCORE_BASE_PORT=7100 dotnet SystemdTestApp.dll &
socat TCP-LISTEN:8083,fork UNIX-CLIENT:/tmp/activate-kestrel.sock &
socat TCP-LISTEN:8084,fork TCP-CONNECT:127.0.0.1:7100 &
socat TCP-LISTEN:8085,fork TCP-CONNECT:127.0.0.1:7101 &

View File

@ -4,14 +4,14 @@ set -e
scriptDir=$(dirname "${BASH_SOURCE[0]}")
PATH="$HOME/.dotnet/:$PATH"
dotnet publish -f netcoreapp2.0 ./samples/SampleApp/
cp -R ./samples/SampleApp/bin/Debug/netcoreapp2.0/publish/ $scriptDir
dotnet publish -f netcoreapp2.0 ./samples/SystemdTestApp/
cp -R ./samples/SystemdTestApp/bin/Debug/netcoreapp2.0/publish/ $scriptDir
cp -R ~/.dotnet/ $scriptDir
image=$(docker build -qf $scriptDir/Dockerfile $scriptDir)
container=$(docker run -Pd $image)
# Try to connect to SampleApp once a second up to 10 times via all available ports.
# Try to connect to SystemdTestApp once a second up to 10 times via all available ports.
for i in {1..10}; do
curl -f http://$(docker port $container 8080/tcp) \
&& curl -f http://$(docker port $container 8081/tcp) \

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using System.Security.Cryptography.X509Certificates;
namespace Microsoft.AspNetCore.Testing
{
@ -11,5 +12,10 @@ namespace Microsoft.AspNetCore.Testing
public static string TestCertificatePath { get; } = Path.Combine(_baseDir, "testCert.pfx");
public static string GetCertPath(string name) => Path.Combine(_baseDir, name);
public static X509Certificate2 GetTestCertificate()
{
return new X509Certificate2(TestCertificatePath, "testPassword");
}
}
}