1
- // Copyright (c) Umbraco.
1
+ // Copyright (c) Umbraco.
2
2
// See LICENSE for more details.
3
3
4
4
using System ;
17
17
namespace Umbraco . Cms . Core . HealthChecks . Checks . Security
18
18
{
19
19
/// <summary>
20
- /// Health checks for the recommended production setup regarding https .
20
+ /// Health checks for the recommended production setup regarding HTTPS .
21
21
/// </summary>
22
22
[ HealthCheck (
23
23
"EB66BB3B-1BCD-4314-9531-9DA2C1D6D9A7" ,
@@ -26,17 +26,26 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Security
26
26
Group = "Security" ) ]
27
27
public class HttpsCheck : HealthCheck
28
28
{
29
+ private const int NumberOfDaysForExpiryWarning = 14 ;
30
+ private const string HttpPropertyKeyCertificateDaysToExpiry = "CertificateDaysToExpiry" ;
31
+
29
32
private readonly ILocalizedTextService _textService ;
30
33
private readonly IOptionsMonitor < GlobalSettings > _globalSettings ;
31
34
private readonly IHostingEnvironment _hostingEnvironment ;
32
35
33
36
private static HttpClient s_httpClient ;
34
- private static HttpClientHandler s_httpClientHandler ;
35
- private static int s_certificateDaysToExpiry ;
37
+
38
+ private static HttpClient HttpClient => s_httpClient ??= new HttpClient ( new HttpClientHandler ( )
39
+ {
40
+ ServerCertificateCustomValidationCallback = ServerCertificateCustomValidation
41
+ } ) ;
36
42
37
43
/// <summary>
38
- /// Initializes a new instance of the <see cref="HttpsCheck"/> class.
44
+ /// Initializes a new instance of the <see cref="HttpsCheck" /> class.
39
45
/// </summary>
46
+ /// <param name="textService">The text service.</param>
47
+ /// <param name="globalSettings">The global settings.</param>
48
+ /// <param name="hostingEnvironment">The hosting environment.</param>
40
49
public HttpsCheck (
41
50
ILocalizedTextService textService ,
42
51
IOptionsMonitor < GlobalSettings > globalSettings ,
@@ -47,33 +56,22 @@ public HttpsCheck(
47
56
_hostingEnvironment = hostingEnvironment ;
48
57
}
49
58
50
- private static HttpClient HttpClient => s_httpClient ??= new HttpClient ( HttpClientHandler ) ;
51
-
52
- private static HttpClientHandler HttpClientHandler => s_httpClientHandler ??= new HttpClientHandler ( )
53
- {
54
- ServerCertificateCustomValidationCallback = ServerCertificateCustomValidation
55
- } ;
56
-
57
- /// <summary>
58
- /// Get the status for this health check
59
- /// </summary>
59
+ /// <inheritdoc />
60
60
public override async Task < IEnumerable < HealthCheckStatus > > GetStatus ( ) =>
61
61
await Task . WhenAll (
62
62
CheckIfCurrentSchemeIsHttps ( ) ,
63
63
CheckHttpsConfigurationSetting ( ) ,
64
64
CheckForValidCertificate ( ) ) ;
65
65
66
- /// <summary>
67
- /// Executes the action and returns it's status
68
- /// </summary>
66
+ /// <inheritdoc />
69
67
public override HealthCheckStatus ExecuteAction ( HealthCheckAction action )
70
68
=> throw new InvalidOperationException ( "HttpsCheck action requested is either not executable or does not exist" ) ;
71
69
72
70
private static bool ServerCertificateCustomValidation ( HttpRequestMessage requestMessage , X509Certificate2 certificate , X509Chain chain , SslPolicyErrors sslErrors )
73
71
{
74
- if ( ! ( certificate is null ) && s_certificateDaysToExpiry == default )
72
+ if ( certificate is not null )
75
73
{
76
- s_certificateDaysToExpiry = ( int ) Math . Floor ( ( certificate . NotAfter - DateTime . Now ) . TotalDays ) ;
74
+ requestMessage . Properties [ HttpPropertyKeyCertificateDaysToExpiry ] = ( int ) Math . Floor ( ( certificate . NotAfter - DateTime . Now ) . TotalDays ) ;
77
75
}
78
76
79
77
return sslErrors == SslPolicyErrors . None ;
@@ -84,30 +82,37 @@ private async Task<HealthCheckStatus> CheckForValidCertificate()
84
82
string message ;
85
83
StatusResultType result ;
86
84
87
- // Attempt to access the site over HTTPS to see if it HTTPS is supported
88
- // and a valid certificate has been configured
89
- var url = _hostingEnvironment . ApplicationMainUrl . ToString ( ) . Replace ( "http:" , "https:" ) ;
85
+ // Attempt to access the site over HTTPS to see if it HTTPS is supported and a valid certificate has been configured
86
+ var urlBuilder = new UriBuilder ( _hostingEnvironment . ApplicationMainUrl )
87
+ {
88
+ Scheme = Uri . UriSchemeHttps
89
+ } ;
90
+ var url = urlBuilder . Uri ;
90
91
91
92
var request = new HttpRequestMessage ( HttpMethod . Head , url ) ;
92
93
93
94
try
94
95
{
95
96
using HttpResponseMessage response = await HttpClient . SendAsync ( request ) ;
97
+
96
98
if ( response . StatusCode == HttpStatusCode . OK )
97
99
{
98
- // Got a valid response, check now for if certificate expiring within 14 days
99
- // Hat-tip: https://stackoverflow.com/a/15343898/489433
100
- const int numberOfDaysForExpiryWarning = 14 ;
100
+ // Got a valid response, check now if the certificate is expiring within the specified amount of days
101
+ int daysToExpiry = 0 ;
102
+ if ( request . Properties . TryGetValue ( HttpPropertyKeyCertificateDaysToExpiry , out var certificateDaysToExpiry ) )
103
+ {
104
+ daysToExpiry = ( int ) certificateDaysToExpiry ;
105
+ }
101
106
102
- if ( s_certificateDaysToExpiry <= 0 )
107
+ if ( daysToExpiry <= 0 )
103
108
{
104
109
result = StatusResultType . Error ;
105
110
message = _textService . Localize ( "healthcheck" , "httpsCheckExpiredCertificate" ) ;
106
111
}
107
- else if ( s_certificateDaysToExpiry < numberOfDaysForExpiryWarning )
112
+ else if ( daysToExpiry < NumberOfDaysForExpiryWarning )
108
113
{
109
114
result = StatusResultType . Warning ;
110
- message = _textService . Localize ( "healthcheck" , "httpsCheckExpiringCertificate" , new [ ] { s_certificateDaysToExpiry . ToString ( ) } ) ;
115
+ message = _textService . Localize ( "healthcheck" , "httpsCheckExpiringCertificate" , new [ ] { daysToExpiry . ToString ( ) } ) ;
111
116
}
112
117
else
113
118
{
@@ -118,21 +123,20 @@ private async Task<HealthCheckStatus> CheckForValidCertificate()
118
123
else
119
124
{
120
125
result = StatusResultType . Error ;
121
- message = _textService . Localize ( "healthcheck" , "healthCheckInvalidUrl" , new [ ] { url , response . ReasonPhrase } ) ;
126
+ message = _textService . Localize ( "healthcheck" , "healthCheckInvalidUrl" , new [ ] { url . AbsoluteUri , response . ReasonPhrase } ) ;
122
127
}
123
128
}
124
129
catch ( Exception ex )
125
130
{
126
- var exception = ex as WebException ;
127
- if ( exception != null )
131
+ if ( ex is WebException exception )
128
132
{
129
133
message = exception . Status == WebExceptionStatus . TrustFailure
130
- ? _textService . Localize ( "healthcheck" , "httpsCheckInvalidCertificate" , new [ ] { exception . Message } )
131
- : _textService . Localize ( "healthcheck" , "healthCheckInvalidUrl" , new [ ] { url , exception . Message } ) ;
134
+ ? _textService . Localize ( "healthcheck" , "httpsCheckInvalidCertificate" , new [ ] { exception . Message } )
135
+ : _textService . Localize ( "healthcheck" , "healthCheckInvalidUrl" , new [ ] { url . AbsoluteUri , exception . Message } ) ;
132
136
}
133
137
else
134
138
{
135
- message = _textService . Localize ( "healthcheck" , "healthCheckInvalidUrl" , new [ ] { url , ex . Message } ) ;
139
+ message = _textService . Localize ( "healthcheck" , "healthCheckInvalidUrl" , new [ ] { url . AbsoluteUri , ex . Message } ) ;
136
140
}
137
141
138
142
result = StatusResultType . Error ;
@@ -150,7 +154,7 @@ private async Task<HealthCheckStatus> CheckForValidCertificate()
150
154
private Task < HealthCheckStatus > CheckIfCurrentSchemeIsHttps ( )
151
155
{
152
156
Uri uri = _hostingEnvironment . ApplicationMainUrl ;
153
- var success = uri . Scheme == "https" ;
157
+ var success = uri . Scheme == Uri . UriSchemeHttps ;
154
158
155
159
return Task . FromResult ( new HealthCheckStatus ( _textService . Localize ( "healthcheck" , "httpsCheckIsCurrentSchemeHttps" , new [ ] { success ? string . Empty : "not" } ) )
156
160
{
@@ -166,16 +170,14 @@ private Task<HealthCheckStatus> CheckHttpsConfigurationSetting()
166
170
167
171
string resultMessage ;
168
172
StatusResultType resultType ;
169
- if ( uri . Scheme != "https" )
173
+ if ( uri . Scheme != Uri . UriSchemeHttps )
170
174
{
171
175
resultMessage = _textService . Localize ( "healthcheck" , "httpsCheckConfigurationRectifyNotPossible" ) ;
172
176
resultType = StatusResultType . Info ;
173
177
}
174
178
else
175
179
{
176
- resultMessage = _textService . Localize (
177
- "healthcheck" , "httpsCheckConfigurationCheckResult" ,
178
- new [ ] { httpsSettingEnabled . ToString ( ) , httpsSettingEnabled ? string . Empty : "not" } ) ;
180
+ resultMessage = _textService . Localize ( "healthcheck" , "httpsCheckConfigurationCheckResult" , new [ ] { httpsSettingEnabled . ToString ( ) , httpsSettingEnabled ? string . Empty : "not" } ) ;
179
181
resultType = httpsSettingEnabled ? StatusResultType . Success : StatusResultType . Error ;
180
182
}
181
183
0 commit comments