Load ClientCertificateMode from config #18660 (#24076)

This commit is contained in:
Kahbazi 2020-07-23 06:27:06 +04:30 committed by GitHub
parent 7a9707eb98
commit 8b7972064f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 206 additions and 6 deletions

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Security.Authentication; using System.Security.Authentication;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
@ -18,6 +19,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
private const string EndpointDefaultsKey = "EndpointDefaults"; private const string EndpointDefaultsKey = "EndpointDefaults";
private const string EndpointsKey = "Endpoints"; private const string EndpointsKey = "Endpoints";
private const string UrlKey = "Url"; private const string UrlKey = "Url";
private const string ClientCertificateModeKey = "ClientCertificateMode";
private readonly IConfiguration _configuration; private readonly IConfiguration _configuration;
@ -50,6 +52,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
// "EndpointDefaults": { // "EndpointDefaults": {
// "Protocols": "Http1AndHttp2", // "Protocols": "Http1AndHttp2",
// "SslProtocols": [ "Tls11", "Tls12", "Tls13"], // "SslProtocols": [ "Tls11", "Tls12", "Tls13"],
// "ClientCertificateMode" : "NoCertificate"
// } // }
private EndpointDefaults ReadEndpointDefaults() private EndpointDefaults ReadEndpointDefaults()
{ {
@ -57,7 +60,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
return new EndpointDefaults return new EndpointDefaults
{ {
Protocols = ParseProtocols(configSection[ProtocolsKey]), Protocols = ParseProtocols(configSection[ProtocolsKey]),
SslProtocols = ParseSslProcotols(configSection.GetSection(SslProtocolsKey)) SslProtocols = ParseSslProcotols(configSection.GetSection(SslProtocolsKey)),
ClientCertificateMode = ParseClientCertificateMode(configSection[ClientCertificateModeKey])
}; };
} }
@ -75,7 +79,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
// "Certificate": { // "Certificate": {
// "Path": "testCert.pfx", // "Path": "testCert.pfx",
// "Password": "testPassword" // "Password": "testPassword"
// } // },
// "ClientCertificateMode" : "NoCertificate"
// } // }
var url = endpointConfig[UrlKey]; var url = endpointConfig[UrlKey];
@ -91,7 +96,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
Protocols = ParseProtocols(endpointConfig[ProtocolsKey]), Protocols = ParseProtocols(endpointConfig[ProtocolsKey]),
ConfigSection = endpointConfig, ConfigSection = endpointConfig,
Certificate = new CertificateConfig(endpointConfig.GetSection(CertificateKey)), Certificate = new CertificateConfig(endpointConfig.GetSection(CertificateKey)),
SslProtocols = ParseSslProcotols(endpointConfig.GetSection(SslProtocolsKey)) SslProtocols = ParseSslProcotols(endpointConfig.GetSection(SslProtocolsKey)),
ClientCertificateMode = ParseClientCertificateMode(endpointConfig[ClientCertificateModeKey])
}; };
endpoints.Add(endpoint); endpoints.Add(endpoint);
@ -100,6 +106,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
return endpoints; return endpoints;
} }
private ClientCertificateMode? ParseClientCertificateMode(string clientCertificateMode)
{
if (Enum.TryParse<ClientCertificateMode>(clientCertificateMode, ignoreCase: true, out var result))
{
return result;
}
return null;
}
private static HttpProtocols? ParseProtocols(string protocols) private static HttpProtocols? ParseProtocols(string protocols)
{ {
if (Enum.TryParse<HttpProtocols>(protocols, ignoreCase: true, out var result)) if (Enum.TryParse<HttpProtocols>(protocols, ignoreCase: true, out var result))
@ -129,11 +145,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
// "EndpointDefaults": { // "EndpointDefaults": {
// "Protocols": "Http1AndHttp2", // "Protocols": "Http1AndHttp2",
// "SslProtocols": [ "Tls11", "Tls12", "Tls13"], // "SslProtocols": [ "Tls11", "Tls12", "Tls13"],
// "ClientCertificateMode" : "NoCertificate"
// } // }
internal class EndpointDefaults internal class EndpointDefaults
{ {
public HttpProtocols? Protocols { get; set; } public HttpProtocols? Protocols { get; set; }
public SslProtocols? SslProtocols { get; set; } public SslProtocols? SslProtocols { get; set; }
public ClientCertificateMode? ClientCertificateMode { get; set; }
} }
// "EndpointName": { // "EndpointName": {
@ -143,7 +161,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
// "Certificate": { // "Certificate": {
// "Path": "testCert.pfx", // "Path": "testCert.pfx",
// "Password": "testPassword" // "Password": "testPassword"
// } // },
// "ClientCertificateMode" : "NoCertificate"
// } // }
internal class EndpointConfig internal class EndpointConfig
{ {
@ -155,6 +174,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public HttpProtocols? Protocols { get; set; } public HttpProtocols? Protocols { get; set; }
public SslProtocols? SslProtocols { get; set; } public SslProtocols? SslProtocols { get; set; }
public CertificateConfig Certificate { get; set; } public CertificateConfig Certificate { get; set; }
public ClientCertificateMode? ClientCertificateMode { get; set; }
// Compare config sections because it's accessible to app developers via an Action<EndpointConfiguration> callback. // Compare config sections because it's accessible to app developers via an Action<EndpointConfiguration> callback.
// We cannot rely entirely on comparing config sections for equality, because KestrelConfigurationLoader.Reload() sets // We cannot rely entirely on comparing config sections for equality, because KestrelConfigurationLoader.Reload() sets

View File

@ -280,6 +280,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel
if (https) if (https)
{ {
httpsOptions.SslProtocols = ConfigurationReader.EndpointDefaults.SslProtocols ?? SslProtocols.None; httpsOptions.SslProtocols = ConfigurationReader.EndpointDefaults.SslProtocols ?? SslProtocols.None;
httpsOptions.ClientCertificateMode = ConfigurationReader.EndpointDefaults.ClientCertificateMode ?? ClientCertificateMode.NoCertificate;
// Defaults // Defaults
Options.ApplyHttpsDefaults(httpsOptions); Options.ApplyHttpsDefaults(httpsOptions);
@ -289,6 +290,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel
httpsOptions.SslProtocols = endpoint.SslProtocols.Value; httpsOptions.SslProtocols = endpoint.SslProtocols.Value;
} }
if (endpoint.ClientCertificateMode.HasValue)
{
httpsOptions.ClientCertificateMode = endpoint.ClientCertificateMode.Value;
}
// Specified // Specified
httpsOptions.ServerCertificate = LoadCertificate(endpoint.Certificate, endpoint.Name) httpsOptions.ServerCertificate = LoadCertificate(endpoint.Certificate, endpoint.Name)
?? httpsOptions.ServerCertificate; ?? httpsOptions.ServerCertificate;

View File

@ -7,6 +7,7 @@ using System.Linq;
using System.Security.Authentication; using System.Security.Authentication;
using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Xunit; using Xunit;
@ -154,10 +155,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests
{ {
new KeyValuePair<string, string>("Endpoints:End1:Url", "http://*:5001"), new KeyValuePair<string, string>("Endpoints:End1:Url", "http://*:5001"),
new KeyValuePair<string, string>("Endpoints:End2:Url", "https://*:5002"), new KeyValuePair<string, string>("Endpoints:End2:Url", "https://*:5002"),
new KeyValuePair<string, string>("Endpoints:End2:ClientCertificateMode", "AllowCertificate"),
new KeyValuePair<string, string>("Endpoints:End3:Url", "https://*:5003"), new KeyValuePair<string, string>("Endpoints:End3:Url", "https://*:5003"),
new KeyValuePair<string, string>("Endpoints:End3:ClientCertificateMode", "RequireCertificate"),
new KeyValuePair<string, string>("Endpoints:End3:Certificate:Path", "/path/cert.pfx"), 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:End3:Certificate:Password", "certpassword"),
new KeyValuePair<string, string>("Endpoints:End4:Url", "https://*:5004"), new KeyValuePair<string, string>("Endpoints:End4:Url", "https://*:5004"),
new KeyValuePair<string, string>("Endpoints:End4:ClientCertificateMode", "NoCertificate"),
new KeyValuePair<string, string>("Endpoints:End4:Certificate:Subject", "certsubject"), 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:Store", "certstore"),
new KeyValuePair<string, string>("Endpoints:End4:Certificate:Location", "cetlocation"), new KeyValuePair<string, string>("Endpoints:End4:Certificate:Location", "cetlocation"),
@ -171,6 +175,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests
var end1 = endpoints.First(); var end1 = endpoints.First();
Assert.Equal("End1", end1.Name); Assert.Equal("End1", end1.Name);
Assert.Equal("http://*:5001", end1.Url); Assert.Equal("http://*:5001", end1.Url);
Assert.Null(end1.ClientCertificateMode);
Assert.NotNull(end1.ConfigSection); Assert.NotNull(end1.ConfigSection);
Assert.NotNull(end1.Certificate); Assert.NotNull(end1.Certificate);
Assert.False(end1.Certificate.ConfigSection.Exists()); Assert.False(end1.Certificate.ConfigSection.Exists());
@ -178,6 +183,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests
var end2 = endpoints.Skip(1).First(); var end2 = endpoints.Skip(1).First();
Assert.Equal("End2", end2.Name); Assert.Equal("End2", end2.Name);
Assert.Equal("https://*:5002", end2.Url); Assert.Equal("https://*:5002", end2.Url);
Assert.Equal(ClientCertificateMode.AllowCertificate, end2.ClientCertificateMode);
Assert.NotNull(end2.ConfigSection); Assert.NotNull(end2.ConfigSection);
Assert.NotNull(end2.Certificate); Assert.NotNull(end2.Certificate);
Assert.False(end2.Certificate.ConfigSection.Exists()); Assert.False(end2.Certificate.ConfigSection.Exists());
@ -185,6 +191,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests
var end3 = endpoints.Skip(2).First(); var end3 = endpoints.Skip(2).First();
Assert.Equal("End3", end3.Name); Assert.Equal("End3", end3.Name);
Assert.Equal("https://*:5003", end3.Url); Assert.Equal("https://*:5003", end3.Url);
Assert.Equal(ClientCertificateMode.RequireCertificate, end3.ClientCertificateMode);
Assert.NotNull(end3.ConfigSection); Assert.NotNull(end3.ConfigSection);
Assert.NotNull(end3.Certificate); Assert.NotNull(end3.Certificate);
Assert.True(end3.Certificate.ConfigSection.Exists()); Assert.True(end3.Certificate.ConfigSection.Exists());
@ -197,6 +204,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests
var end4 = endpoints.Skip(3).First(); var end4 = endpoints.Skip(3).First();
Assert.Equal("End4", end4.Name); Assert.Equal("End4", end4.Name);
Assert.Equal("https://*:5004", end4.Url); Assert.Equal("https://*:5004", end4.Url);
Assert.Equal(ClientCertificateMode.NoCertificate, end4.ClientCertificateMode);
Assert.NotNull(end4.ConfigSection); Assert.NotNull(end4.ConfigSection);
Assert.NotNull(end4.Certificate); Assert.NotNull(end4.Certificate);
Assert.True(end4.Certificate.ConfigSection.Exists()); Assert.True(end4.Certificate.ConfigSection.Exists());
@ -235,7 +243,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests
var reader = new ConfigurationReader(config); var reader = new ConfigurationReader(config);
var endpoint = reader.Endpoints.First(); var endpoint = reader.Endpoints.First();
Assert.Equal(SslProtocols.Tls11|SslProtocols.Tls12, endpoint.SslProtocols); Assert.Equal(SslProtocols.Tls11 | SslProtocols.Tls12, endpoint.SslProtocols);
} }
[Fact] [Fact]
@ -287,5 +295,41 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests
var endpoint = reader.EndpointDefaults; var endpoint = reader.EndpointDefaults;
Assert.Null(endpoint.SslProtocols); Assert.Null(endpoint.SslProtocols);
} }
[Fact]
public void ReadEndpointWithNoClientCertificateModeSettings_ReturnsNull()
{
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Endpoints:End1:Url", "http://*:5001"),
}).Build();
var reader = new ConfigurationReader(config);
var endpoint = reader.Endpoints.First();
Assert.Null(endpoint.ClientCertificateMode);
}
[Fact]
public void ReadEndpointDefaultsWithClientCertificateModeSet_ReturnsCorrectValue()
{
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("EndpointDefaults:ClientCertificateMode", "AllowCertificate"),
}).Build();
var reader = new ConfigurationReader(config);
var endpoint = reader.EndpointDefaults;
Assert.Equal(ClientCertificateMode.AllowCertificate, endpoint.ClientCertificateMode);
}
[Fact]
public void ReadEndpointDefaultsWithNoAllowCertificateSettings_ReturnsCorrectValue()
{
var config = new ConfigurationBuilder().Build();
var reader = new ConfigurationReader(config);
var endpoint = reader.EndpointDefaults;
Assert.Null(endpoint.ClientCertificateMode);
}
} }
} }

View File

@ -704,6 +704,136 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests
Assert.True(ran1); Assert.True(ran1);
} }
[Fact]
public void EndpointConfigureSection_CanSetClientCertificateMode()
{
var serverOptions = CreateServerOptions();
var ranDefault = false;
serverOptions.ConfigureHttpsDefaults(opt =>
{
opt.ServerCertificate = TestResources.GetTestCertificate();
// Kestrel default
Assert.Equal(ClientCertificateMode.NoCertificate, opt.ClientCertificateMode);
ranDefault = true;
});
var ran1 = false;
var ran2 = false;
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Endpoints:End1:ClientCertificateMode", "AllowCertificate"),
new KeyValuePair<string, string>("Endpoints:End1:Url", "https://*:5001"),
}).Build();
serverOptions.Configure(config)
.Endpoint("End1", opt =>
{
Assert.Equal(ClientCertificateMode.AllowCertificate, opt.HttpsOptions.ClientCertificateMode);
ran1 = true;
})
.Load();
serverOptions.ListenAnyIP(0, opt =>
{
opt.UseHttps(httpsOptions =>
{
// Kestrel default.
Assert.Equal(ClientCertificateMode.NoCertificate, httpsOptions.ClientCertificateMode);
ran2 = true;
});
});
Assert.True(ranDefault);
Assert.True(ran1);
Assert.True(ran2);
}
[Fact]
public void EndpointConfigureSection_CanOverrideClientCertificateModeFromConfigureHttpsDefaults()
{
var serverOptions = CreateServerOptions();
serverOptions.ConfigureHttpsDefaults(opt =>
{
opt.ServerCertificate = TestResources.GetTestCertificate();
opt.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
});
var ran1 = false;
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Endpoints:End1:ClientCertificateMode", "AllowCertificate"),
new KeyValuePair<string, string>("Endpoints:End1:Url", "https://*:5001"),
}).Build();
serverOptions.Configure(config)
.Endpoint("End1", opt =>
{
Assert.Equal(ClientCertificateMode.AllowCertificate, opt.HttpsOptions.ClientCertificateMode);
ran1 = true;
})
.Load();
Assert.True(ran1);
}
[Fact]
public void DefaultEndpointConfigureSection_CanSetClientCertificateMode()
{
var serverOptions = CreateServerOptions();
serverOptions.ConfigureHttpsDefaults(opt =>
{
opt.ServerCertificate = TestResources.GetTestCertificate();
});
var ran1 = false;
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("EndpointDefaults:ClientCertificateMode", "AllowCertificate"),
new KeyValuePair<string, string>("Endpoints:End1:Url", "https://*:5001"),
}).Build();
serverOptions.Configure(config)
.Endpoint("End1", opt =>
{
Assert.Equal(ClientCertificateMode.AllowCertificate, opt.HttpsOptions.ClientCertificateMode);
ran1 = true;
})
.Load();
Assert.True(ran1);
}
[Fact]
public void DefaultEndpointConfigureSection_ConfigureHttpsDefaultsCanOverrideClientCertificateMode()
{
var serverOptions = CreateServerOptions();
serverOptions.ConfigureHttpsDefaults(opt =>
{
opt.ServerCertificate = TestResources.GetTestCertificate();
Assert.Equal(ClientCertificateMode.AllowCertificate, opt.ClientCertificateMode);
opt.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
});
var ran1 = false;
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("EndpointDefaults:ClientCertificateMode", "AllowCertificate"),
new KeyValuePair<string, string>("Endpoints:End1:Url", "https://*:5001"),
}).Build();
serverOptions.Configure(config)
.Endpoint("End1", opt =>
{
Assert.Equal(ClientCertificateMode.RequireCertificate, opt.HttpsOptions.ClientCertificateMode);
ran1 = true;
})
.Load();
Assert.True(ran1);
}
[Fact] [Fact]
public void Reload_IdentifiesEndpointsToStartAndStop() public void Reload_IdentifiesEndpointsToStartAndStop()
{ {