Files
Umbraco-CMS/src/Umbraco.Web/HealthCheck/Checks/Security/HttpsCheck.cs
2017-09-08 19:39:13 +02:00

204 lines
8.1 KiB
C#

using System;
using System.Collections.Generic;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Web;
using Umbraco.Core;
using Umbraco.Core.IO;
using Umbraco.Core.Services;
using Umbraco.Web.HealthCheck.Checks.Config;
namespace Umbraco.Web.HealthCheck.Checks.Security
{
[HealthCheck(
"EB66BB3B-1BCD-4314-9531-9DA2C1D6D9A7",
"HTTPS Configuration",
Description = "Checks if your site is configured to work over HTTPS and if the Umbraco related configuration for that is correct.",
Group = "Security")]
public class HttpsCheck : HealthCheck
{
private readonly ILocalizedTextService _textService;
private readonly IRuntimeState _runtime;
private const string FixHttpsSettingAction = "fixHttpsSetting";
public HttpsCheck(ILocalizedTextService textService, IRuntimeState runtime)
{
_textService = textService;
_runtime = runtime;
}
/// <summary>
/// Get the status for this health check
/// </summary>
/// <returns></returns>
public override IEnumerable<HealthCheckStatus> GetStatus()
{
//return the statuses
return new[] { CheckIfCurrentSchemeIsHttps(), CheckHttpsConfigurationSetting(), CheckForValidCertificate() };
}
/// <summary>
/// Executes the action and returns it's status
/// </summary>
/// <param name="action"></param>
/// <returns></returns>
public override HealthCheckStatus ExecuteAction(HealthCheckAction action)
{
switch (action.Alias)
{
case FixHttpsSettingAction:
return FixHttpsSetting();
default:
throw new InvalidOperationException("HttpsCheck action requested is either not executable or does not exist");
}
}
private HealthCheckStatus CheckForValidCertificate()
{
var message = string.Empty;
StatusResultType result;
// Attempt to access the site over HTTPS to see if it HTTPS is supported
// and a valid certificate has been configured
var url = _runtime.ApplicationUrl.Replace("http:", "https:");
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "HEAD";
try
{
var response = (HttpWebResponse)request.GetResponse();
if (response.StatusCode == HttpStatusCode.OK)
{
// Got a valid response, check now for if certificate expiring within 14 days
// Hat-tip: https://stackoverflow.com/a/15343898/489433
const int NumberOfDaysForExpiryWarning = 14;
var cert = request.ServicePoint.Certificate;
var cert2 = new X509Certificate2(cert);
var expirationDate = cert2.NotAfter;
var daysToExpiry = (int)Math.Floor((cert2.NotAfter - DateTime.Now).TotalDays);
if (daysToExpiry <= 0)
{
result = StatusResultType.Error;
message = _textService.Localize("healthcheck/httpsCheckExpiredCertificate");
}
else if (daysToExpiry < NumberOfDaysForExpiryWarning)
{
result = StatusResultType.Warning;
message = _textService.Localize("healthcheck/httpsCheckExpiringCertificate", new[] { daysToExpiry.ToString() });
}
else
{
result = StatusResultType.Success;
message = _textService.Localize("healthcheck/httpsCheckValidCertificate");
}
}
else
{
result = StatusResultType.Error;
message = _textService.Localize("healthcheck/httpsCheckInvalidUrl", new[] { url, response.StatusDescription });
}
}
catch (Exception ex)
{
var exception = ex as WebException;
if (exception != null)
{
message = exception.Status == WebExceptionStatus.TrustFailure
? _textService.Localize("healthcheck/httpsCheckInvalidCertificate", new [] { exception.Message })
: _textService.Localize("healthcheck/httpsCheckInvalidUrl", new [] { url, exception.Message });
}
else
{
message = _textService.Localize("healthcheck/httpsCheckInvalidUrl", new[] { url, ex.Message });
}
result = StatusResultType.Error;
}
var actions = new List<HealthCheckAction>();
return
new HealthCheckStatus(message)
{
ResultType = result,
Actions = actions
};
}
private HealthCheckStatus CheckIfCurrentSchemeIsHttps()
{
var uri = new Uri(HealthCheckContext.SiteUrl);
var success = uri.Scheme == "https";
var actions = new List<HealthCheckAction>();
return
new HealthCheckStatus(_textService.Localize("healthcheck/httpsCheckIsCurrentSchemeHttps", new[] { success ? string.Empty : "not" }))
{
ResultType = success ? StatusResultType.Success : StatusResultType.Error,
Actions = actions
};
}
private HealthCheckStatus CheckHttpsConfigurationSetting()
{
var httpsSettingEnabled = Core.Configuration.GlobalSettings.UseSSL;
var uri = new Uri(HealthCheckContext.SiteUrl);
var actions = new List<HealthCheckAction>();
string resultMessage;
StatusResultType resultType;
if (uri.Scheme != "https")
{
resultMessage = _textService.Localize("healthcheck/httpsCheckConfigurationRectifyNotPossible");
resultType = StatusResultType.Info;
}
else
{
if (httpsSettingEnabled == false)
actions.Add(new HealthCheckAction(FixHttpsSettingAction, Id)
{
Name = _textService.Localize("healthcheck/httpsCheckEnableHttpsButton"),
Description = _textService.Localize("healthcheck/httpsCheckEnableHttpsDescription")
});
resultMessage = _textService.Localize("healthcheck/httpsCheckConfigurationCheckResult",
new[] {httpsSettingEnabled.ToString(), httpsSettingEnabled ? string.Empty : "not"});
resultType = httpsSettingEnabled ? StatusResultType.Success: StatusResultType.Error;
}
return
new HealthCheckStatus(resultMessage)
{
ResultType = resultType,
Actions = actions
};
}
private HealthCheckStatus FixHttpsSetting()
{
var configFile = IOHelper.MapPath("~/Web.config");
const string xPath = "/configuration/appSettings/add[@key='umbracoUseSSL']/@value";
var configurationService = new ConfigurationService(configFile, xPath, _textService);
var updateConfigFile = configurationService.UpdateConfigFile("true");
if (updateConfigFile.Success)
{
return
new HealthCheckStatus(_textService.Localize("healthcheck/httpsCheckEnableHttpsSuccess"))
{
ResultType = StatusResultType.Success
};
}
return
new HealthCheckStatus(_textService.Localize("healthcheck/httpsCheckEnableHttpsError", new [] { updateConfigFile.Result }))
{
ResultType = StatusResultType.Error
};
}
}
}