1
0
mirror of https://github.com/Radarr/Radarr.git synced 2024-11-04 10:02:40 +01:00

Fixed: Ensure SSL cert exists before saving config

Trap missing certificate exception to avoid bootloop

Fixes #4403
This commit is contained in:
ta264 2020-05-14 19:24:41 +01:00
parent 6ad3653c04
commit 78c7372a0d
3 changed files with 71 additions and 3 deletions

View File

@ -0,0 +1,26 @@
using FluentValidation.Validators;
using NzbDrone.Common.Disk;
namespace NzbDrone.Core.Validation.Paths
{
public class FileExistsValidator : PropertyValidator
{
private readonly IDiskProvider _diskProvider;
public FileExistsValidator(IDiskProvider diskProvider)
: base("File does not exist")
{
_diskProvider = diskProvider;
}
protected override bool IsValid(PropertyValidatorContext context)
{
if (context.PropertyValue == null)
{
return false;
}
return _diskProvider.FileExists(context.PropertyValue.ToString());
}
}
}

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
@ -10,6 +11,7 @@
using NLog; using NLog;
using NLog.Extensions.Logging; using NLog.Extensions.Logging;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Exceptions;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Serializer; using NzbDrone.Common.Serializer;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
@ -72,7 +74,21 @@ public void StartServer()
{ {
options.ConfigureHttpsDefaults(configureOptions => options.ConfigureHttpsDefaults(configureOptions =>
{ {
var certificate = new X509Certificate2(_configFileProvider.SslCertPath, _configFileProvider.SslCertPassword, X509KeyStorageFlags.DefaultKeySet); X509Certificate2 certificate;
try
{
certificate = new X509Certificate2(sslCertPath, _configFileProvider.SslCertPassword, X509KeyStorageFlags.DefaultKeySet);
}
catch (CryptographicException ex)
{
if (ex.HResult == 0x2 || ex.HResult == 0x2006D080)
{
throw new RadarrStartupException(ex, $"The SSL certificate file {sslCertPath} does not exist");
}
throw new RadarrStartupException(ex);
}
configureOptions.ServerCertificate = certificate; configureOptions.ServerCertificate = certificate;
}); });

View File

@ -1,6 +1,7 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using FluentValidation; using FluentValidation;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Authentication; using NzbDrone.Core.Authentication;
@ -18,7 +19,10 @@ public class HostConfigModule : RadarrRestModule<HostConfigResource>
private readonly IConfigService _configService; private readonly IConfigService _configService;
private readonly IUserService _userService; private readonly IUserService _userService;
public HostConfigModule(IConfigFileProvider configFileProvider, IConfigService configService, IUserService userService) public HostConfigModule(IConfigFileProvider configFileProvider,
IConfigService configService,
IUserService userService,
FileExistsValidator fileExistsValidator)
: base("/config/host") : base("/config/host")
{ {
_configFileProvider = configFileProvider; _configFileProvider = configFileProvider;
@ -43,7 +47,14 @@ public HostConfigModule(IConfigFileProvider configFileProvider, IConfigService c
SharedValidator.RuleFor(c => c.SslPort).ValidPort().When(c => c.EnableSsl); SharedValidator.RuleFor(c => c.SslPort).ValidPort().When(c => c.EnableSsl);
SharedValidator.RuleFor(c => c.SslPort).NotEqual(c => c.Port).When(c => c.EnableSsl); SharedValidator.RuleFor(c => c.SslPort).NotEqual(c => c.Port).When(c => c.EnableSsl);
SharedValidator.RuleFor(c => c.SslCertPath).NotEmpty().When(c => c.EnableSsl);
SharedValidator.RuleFor(c => c.SslCertPath)
.Cascade(CascadeMode.StopOnFirstFailure)
.NotEmpty()
.IsValidPath()
.SetValidator(fileExistsValidator)
.Must((resource, path) => IsValidSslCertificate(resource)).WithMessage("Invalid SSL certificate file or password")
.When(c => c.EnableSsl);
SharedValidator.RuleFor(c => c.Branch).NotEmpty().WithMessage("Branch name is required, 'master' is the default"); SharedValidator.RuleFor(c => c.Branch).NotEmpty().WithMessage("Branch name is required, 'master' is the default");
SharedValidator.RuleFor(c => c.UpdateScriptPath).IsValidPath().When(c => c.UpdateMechanism == UpdateMechanism.Script); SharedValidator.RuleFor(c => c.UpdateScriptPath).IsValidPath().When(c => c.UpdateMechanism == UpdateMechanism.Script);
@ -53,6 +64,21 @@ public HostConfigModule(IConfigFileProvider configFileProvider, IConfigService c
SharedValidator.RuleFor(c => c.BackupRetention).InclusiveBetween(1, 90); SharedValidator.RuleFor(c => c.BackupRetention).InclusiveBetween(1, 90);
} }
private bool IsValidSslCertificate(HostConfigResource resource)
{
X509Certificate2 cert;
try
{
cert = new X509Certificate2(resource.SslCertPath, resource.SslCertPassword, X509KeyStorageFlags.DefaultKeySet);
}
catch
{
return false;
}
return cert != null;
}
private HostConfigResource GetHostConfig() private HostConfigResource GetHostConfig()
{ {
var resource = _configFileProvider.ToResource(_configService); var resource = _configFileProvider.ToResource(_configService);