Kestrel Endpoints' "SslProtocols" settable via config (#22663) (#22910)

This commit is contained in:
Filip Staffa 2020-06-19 17:09:56 +02:00 committed by GitHub
parent 23595db0d2
commit a77e68f1c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 248 additions and 2 deletions

View File

@ -3,6 +3,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Authentication;
using Microsoft.Extensions.Configuration;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
@ -12,6 +14,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
private const string ProtocolsKey = "Protocols";
private const string CertificatesKey = "Certificates";
private const string CertificateKey = "Certificate";
private const string SslProtocolsKey = "SslProtocols";
private const string EndpointDefaultsKey = "EndpointDefaults";
private const string EndpointsKey = "Endpoints";
private const string UrlKey = "Url";
@ -49,13 +52,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
// "EndpointDefaults": {
// "Protocols": "Http1AndHttp2",
// "SslProtocols": [ "Tls11", "Tls12", "Tls13"],
// }
private EndpointDefaults ReadEndpointDefaults()
{
var configSection = _configuration.GetSection(EndpointDefaultsKey);
return new EndpointDefaults
{
Protocols = ParseProtocols(configSection[ProtocolsKey])
Protocols = ParseProtocols(configSection[ProtocolsKey]),
SslProtocols = ParseSslProcotols(configSection.GetSection(SslProtocolsKey))
};
}
@ -69,6 +74,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
// "EndpointName": {
// "Url": "https://*:5463",
// "Protocols": "Http1AndHttp2",
// "SslProtocols": [ "Tls11", "Tls12", "Tls13"],
// "Certificate": {
// "Path": "testCert.pfx",
// "Password": "testPassword"
@ -88,6 +94,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
Protocols = ParseProtocols(endpointConfig[ProtocolsKey]),
ConfigSection = endpointConfig,
Certificate = new CertificateConfig(endpointConfig.GetSection(CertificateKey)),
SslProtocols = ParseSslProcotols(endpointConfig.GetSection(SslProtocolsKey))
};
endpoints.Add(endpoint);
@ -105,19 +112,37 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
return null;
}
private static SslProtocols? ParseSslProcotols(IConfigurationSection sslProtocols)
{
var stringProtocols = sslProtocols.Get<string[]>();
return stringProtocols?.Aggregate(SslProtocols.None, (acc, current) =>
{
if (Enum.TryParse(current, ignoreCase: true, out SslProtocols parsed))
{
return acc | parsed;
}
return acc;
});
}
}
// "EndpointDefaults": {
// "Protocols": "Http1AndHttp2",
// "SslProtocols": [ "Tls11", "Tls12", "Tls13"],
// }
internal class EndpointDefaults
{
public HttpProtocols? Protocols { get; set; }
public SslProtocols? SslProtocols { get; set; }
}
// "EndpointName": {
// "Url": "https://*:5463",
// "Protocols": "Http1AndHttp2",
// "SslProtocols": [ "Tls11", "Tls12", "Tls13"],
// "Certificate": {
// "Path": "testCert.pfx",
// "Password": "testPassword"
@ -131,6 +156,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
public string Name { get; set; }
public string Url { get; set; }
public HttpProtocols? Protocols { get; set; }
public SslProtocols? SslProtocols { get; set; }
public CertificateConfig Certificate { get; set; }
// Compare config sections because it's accessible to app developers via an Action<EndpointConfiguration> callback.
@ -154,6 +180,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
Url == other.Url &&
(Protocols ?? ListenOptions.DefaultHttpProtocols) == (other.Protocols ?? ListenOptions.DefaultHttpProtocols) &&
Certificate == other.Certificate &&
(SslProtocols ?? System.Security.Authentication.SslProtocols.None) == (other.SslProtocols ?? System.Security.Authentication.SslProtocols.None) &&
_configSectionClone == other._configSectionClone;
public override int GetHashCode() => HashCode.Combine(Name, Url, Protocols ?? ListenOptions.DefaultHttpProtocols, Certificate, _configSectionClone);

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Certificates.Generation;
@ -279,9 +280,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel
var httpsOptions = new HttpsConnectionAdapterOptions();
if (https)
{
httpsOptions.SslProtocols = ConfigurationReader.EndpointDefaults.SslProtocols ?? SslProtocols.None;
// Defaults
Options.ApplyHttpsDefaults(httpsOptions);
if (endpoint.SslProtocols.HasValue)
{
httpsOptions.SslProtocols = endpoint.SslProtocols.Value;
}
// Specified
httpsOptions.ServerCertificate = LoadCertificate(endpoint.Certificate, endpoint.Name)
?? httpsOptions.ServerCertificate;

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Authentication;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.Extensions.Configuration;
using Xunit;
@ -173,5 +174,84 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests
Assert.Equal("cetlocation", cert4.Location);
Assert.True(cert4.AllowInvalid);
}
[Fact]
public void ReadEndpointWithSingleSslProtocolSet_ReturnsCorrectValue()
{
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Endpoints:End1:Url", "http://*:5001"),
new KeyValuePair<string, string>("Endpoints:End1:SslProtocols:0", "Tls11"),
}).Build();
var reader = new ConfigurationReader(config);
var endpoint = reader.Endpoints.First();
Assert.Equal(SslProtocols.Tls11, endpoint.SslProtocols);
}
[Fact]
public void ReadEndpointWithMultipleSslProtocolsSet_ReturnsCorrectValue()
{
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Endpoints:End1:Url", "http://*:5001"),
new KeyValuePair<string, string>("Endpoints:End1:SslProtocols:0", "Tls11"),
new KeyValuePair<string, string>("Endpoints:End1:SslProtocols:1", "Tls12"),
}).Build();
var reader = new ConfigurationReader(config);
var endpoint = reader.Endpoints.First();
Assert.Equal(SslProtocols.Tls11|SslProtocols.Tls12, endpoint.SslProtocols);
}
[Fact]
public void ReadEndpointWithSslProtocolSet_ReadsCaseInsensitive()
{
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Endpoints:End1:Url", "http://*:5001"),
new KeyValuePair<string, string>("Endpoints:End1:SslProtocols:0", "TLS11"),
}).Build();
var reader = new ConfigurationReader(config);
var endpoint = reader.Endpoints.First();
Assert.Equal(SslProtocols.Tls11, endpoint.SslProtocols);
}
[Fact]
public void ReadEndpointWithNoSslProtocolSettings_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.SslProtocols);
}
[Fact]
public void ReadEndpointDefaultsWithSingleSslProtocolSet_ReturnsCorrectValue()
{
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("EndpointDefaults:SslProtocols:0", "Tls11"),
}).Build();
var reader = new ConfigurationReader(config);
var endpoint = reader.EndpointDefaults;
Assert.Equal(SslProtocols.Tls11, endpoint.SslProtocols);
}
[Fact]
public void ReadEndpointDefaultsWithNoSslProtocolSettings_ReturnsCorrectValue()
{
var config = new ConfigurationBuilder().Build();
var reader = new ConfigurationReader(config);
var endpoint = reader.EndpointDefaults;
Assert.Null(endpoint.SslProtocols);
}
}
}

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
@ -18,7 +19,7 @@ using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.Tests
{
public class KestrelConfigurationBuilderTests
public class KestrelConfigurationLoaderTests
{
private KestrelServerOptions CreateServerOptions()
{
@ -456,6 +457,136 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Tests
Assert.True(ran3);
}
[Fact]
public void EndpointConfigureSection_CanSetSslProtocol()
{
var serverOptions = CreateServerOptions();
var ranDefault = false;
serverOptions.ConfigureHttpsDefaults(opt =>
{
opt.ServerCertificate = TestResources.GetTestCertificate();
// Kestrel default
Assert.Equal(SslProtocols.None, opt.SslProtocols);
ranDefault = true;
});
var ran1 = false;
var ran2 = false;
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Endpoints:End1:SslProtocols:0", "Tls11"),
new KeyValuePair<string, string>("Endpoints:End1:Url", "https://*:5001"),
}).Build();
serverOptions.Configure(config)
.Endpoint("End1", opt =>
{
Assert.Equal(SslProtocols.Tls11, opt.HttpsOptions.SslProtocols);
ran1 = true;
})
.Load();
serverOptions.ListenAnyIP(0, opt =>
{
opt.UseHttps(httpsOptions =>
{
// Kestrel default.
Assert.Equal(SslProtocols.None, httpsOptions.SslProtocols);
ran2 = true;
});
});
Assert.True(ranDefault);
Assert.True(ran1);
Assert.True(ran2);
}
[Fact]
public void EndpointConfigureSection_CanOverrideSslProtocolsFromConfigureHttpsDefaults()
{
var serverOptions = CreateServerOptions();
serverOptions.ConfigureHttpsDefaults(opt =>
{
opt.ServerCertificate = TestResources.GetTestCertificate();
opt.SslProtocols = SslProtocols.Tls12;
});
var ran1 = false;
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Endpoints:End1:SslProtocols:0", "Tls11"),
new KeyValuePair<string, string>("Endpoints:End1:Url", "https://*:5001"),
}).Build();
serverOptions.Configure(config)
.Endpoint("End1", opt =>
{
Assert.Equal(SslProtocols.Tls11, opt.HttpsOptions.SslProtocols);
ran1 = true;
})
.Load();
Assert.True(ran1);
}
[Fact]
public void DefaultEndpointConfigureSection_CanSetSslProtocols()
{
var serverOptions = CreateServerOptions();
serverOptions.ConfigureHttpsDefaults(opt =>
{
opt.ServerCertificate = TestResources.GetTestCertificate();
});
var ran1 = false;
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("EndpointDefaults:SslProtocols:0", "Tls11"),
new KeyValuePair<string, string>("Endpoints:End1:Url", "https://*:5001"),
}).Build();
serverOptions.Configure(config)
.Endpoint("End1", opt =>
{
Assert.Equal(SslProtocols.Tls11, opt.HttpsOptions.SslProtocols);
ran1 = true;
})
.Load();
Assert.True(ran1);
}
[Fact]
public void DefaultEndpointConfigureSection_ConfigureHttpsDefaultsCanOverrideSslProtocols()
{
var serverOptions = CreateServerOptions();
serverOptions.ConfigureHttpsDefaults(opt =>
{
opt.ServerCertificate = TestResources.GetTestCertificate();
Assert.Equal(SslProtocols.Tls11, opt.SslProtocols);
opt.SslProtocols = SslProtocols.Tls12;
});
var ran1 = false;
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("EndpointDefaults:SslProtocols:0", "Tls11"),
new KeyValuePair<string, string>("Endpoints:End1:Url", "https://*:5001"),
}).Build();
serverOptions.Configure(config)
.Endpoint("End1", opt =>
{
Assert.Equal(SslProtocols.Tls12, opt.HttpsOptions.SslProtocols);
ran1 = true;
})
.Load();
Assert.True(ran1);
}
[Fact]
public void Latin1RequestHeadersReadFromConfig()
{