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.Linq;
using System.Security.Authentication;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.Extensions.Configuration;
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 EndpointsKey = "Endpoints";
private const string UrlKey = "Url";
private const string ClientCertificateModeKey = "ClientCertificateMode";
private readonly IConfiguration _configuration;
@ -50,6 +52,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
// "EndpointDefaults": {
// "Protocols": "Http1AndHttp2",
// "SslProtocols": [ "Tls11", "Tls12", "Tls13"],
// "ClientCertificateMode" : "NoCertificate"
// }
private EndpointDefaults ReadEndpointDefaults()
{
@ -57,7 +60,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
return new EndpointDefaults
{
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": {
// "Path": "testCert.pfx",
// "Password": "testPassword"
// }
// },
// "ClientCertificateMode" : "NoCertificate"
// }
var url = endpointConfig[UrlKey];
@ -91,7 +96,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
Protocols = ParseProtocols(endpointConfig[ProtocolsKey]),
ConfigSection = endpointConfig,
Certificate = new CertificateConfig(endpointConfig.GetSection(CertificateKey)),
SslProtocols = ParseSslProcotols(endpointConfig.GetSection(SslProtocolsKey))
SslProtocols = ParseSslProcotols(endpointConfig.GetSection(SslProtocolsKey)),
ClientCertificateMode = ParseClientCertificateMode(endpointConfig[ClientCertificateModeKey])
};
endpoints.Add(endpoint);
@ -100,6 +106,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
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)
{
if (Enum.TryParse<HttpProtocols>(protocols, ignoreCase: true, out var result))
@ -129,11 +145,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
// "EndpointDefaults": {
// "Protocols": "Http1AndHttp2",
// "SslProtocols": [ "Tls11", "Tls12", "Tls13"],
// "ClientCertificateMode" : "NoCertificate"
// }
internal class EndpointDefaults
{
public HttpProtocols? Protocols { get; set; }
public SslProtocols? SslProtocols { get; set; }
public ClientCertificateMode? ClientCertificateMode { get; set; }
}
// "EndpointName": {
@ -143,7 +161,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
// "Certificate": {
// "Path": "testCert.pfx",
// "Password": "testPassword"
// }
// },
// "ClientCertificateMode" : "NoCertificate"
// }
internal class EndpointConfig
{
@ -155,6 +174,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public HttpProtocols? Protocols { get; set; }
public SslProtocols? SslProtocols { 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.
// 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)
{
httpsOptions.SslProtocols = ConfigurationReader.EndpointDefaults.SslProtocols ?? SslProtocols.None;
httpsOptions.ClientCertificateMode = ConfigurationReader.EndpointDefaults.ClientCertificateMode ?? ClientCertificateMode.NoCertificate;
// Defaults
Options.ApplyHttpsDefaults(httpsOptions);
@ -289,6 +290,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel
httpsOptions.SslProtocols = endpoint.SslProtocols.Value;
}
if (endpoint.ClientCertificateMode.HasValue)
{
httpsOptions.ClientCertificateMode = endpoint.ClientCertificateMode.Value;
}
// Specified
httpsOptions.ServerCertificate = LoadCertificate(endpoint.Certificate, endpoint.Name)
?? httpsOptions.ServerCertificate;

View File

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