diff options
Diffstat (limited to 'src/main.lib/Plugins/InstallationPlugins')
15 files changed, 548 insertions, 0 deletions
diff --git a/src/main.lib/Plugins/InstallationPlugins/IISFtp/IISFtp.cs b/src/main.lib/Plugins/InstallationPlugins/IISFtp/IISFtp.cs new file mode 100644 index 0000000..7e6ce2d --- /dev/null +++ b/src/main.lib/Plugins/InstallationPlugins/IISFtp/IISFtp.cs @@ -0,0 +1,37 @@ +using PKISharp.WACS.Clients.IIS; +using PKISharp.WACS.DomainObjects; +using PKISharp.WACS.Plugins.Interfaces; +using PKISharp.WACS.Plugins.StorePlugins; +using PKISharp.WACS.Services; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace PKISharp.WACS.Plugins.InstallationPlugins +{ + class IISFtp : IInstallationPlugin + { + private IIISClient _iisClient; + private ILogService _log; + private IISFtpOptions _options; + + public IISFtp(IISFtpOptions options, IIISClient iisClient, ILogService log) + { + _iisClient = iisClient; + _options = options; + _log = log; + } + + void IInstallationPlugin.Install(IEnumerable<IStorePlugin> stores, CertificateInfo newCertificate, CertificateInfo oldCertificate) + { + if (!stores.Any(x => x is CertificateStore)) + { + // Unknown/unsupported store + var errorMessage = "This installation plugin cannot be used in combination with the store plugin"; + _log.Error(errorMessage); + throw new InvalidOperationException(errorMessage); + } + _iisClient.UpdateFtpSite(_options.SiteId, newCertificate, oldCertificate); + } + } +} diff --git a/src/main.lib/Plugins/InstallationPlugins/IISFtp/IISFtpArguments.cs b/src/main.lib/Plugins/InstallationPlugins/IISFtp/IISFtpArguments.cs new file mode 100644 index 0000000..43f7924 --- /dev/null +++ b/src/main.lib/Plugins/InstallationPlugins/IISFtp/IISFtpArguments.cs @@ -0,0 +1,9 @@ +using PKISharp.WACS.Configuration; + +namespace PKISharp.WACS.Plugins.InstallationPlugins +{ + class IISFtpArguments + { + public long? FtpSiteId { get; set; } + } +} diff --git a/src/main.lib/Plugins/InstallationPlugins/IISFtp/IISFtpArgumentsProvider.cs b/src/main.lib/Plugins/InstallationPlugins/IISFtp/IISFtpArgumentsProvider.cs new file mode 100644 index 0000000..20a1f5d --- /dev/null +++ b/src/main.lib/Plugins/InstallationPlugins/IISFtp/IISFtpArgumentsProvider.cs @@ -0,0 +1,25 @@ +using Fclp; +using PKISharp.WACS.Configuration; +using PKISharp.WACS.Services; + +namespace PKISharp.WACS.Plugins.InstallationPlugins +{ + class IISFtpArgumentsProvider : BaseArgumentsProvider<IISFtpArguments> + { + public override string Name => "IIS FTP plugin"; + public override string Group => "Installation"; + public override string Condition => "--installation iisftp"; + + public override void Configure(FluentCommandLineParser<IISFtpArguments> parser) + { + parser.Setup(o => o.FtpSiteId) + .As("ftpsiteid") + .WithDescription("Site id to install certificate to."); + } + + public override bool Active(IISFtpArguments current) + { + return current.FtpSiteId != null; + } + } +} diff --git a/src/main.lib/Plugins/InstallationPlugins/IISFtp/IISFtpOptions.cs b/src/main.lib/Plugins/InstallationPlugins/IISFtp/IISFtpOptions.cs new file mode 100644 index 0000000..4704501 --- /dev/null +++ b/src/main.lib/Plugins/InstallationPlugins/IISFtp/IISFtpOptions.cs @@ -0,0 +1,13 @@ +using PKISharp.WACS.Plugins.Base; +using PKISharp.WACS.Plugins.Base.Options; + +namespace PKISharp.WACS.Plugins.InstallationPlugins +{ + [Plugin("13058a79-5084-48af-b047-634e0ee222f4")] + class IISFtpOptions : InstallationPluginOptions<IISFtp> + { + public long SiteId { get; set; } + public override string Name => "IISFTP"; + public override string Description => "Create or update ftps bindings in IIS"; + } +} diff --git a/src/main.lib/Plugins/InstallationPlugins/IISFtp/IISFtpOptionsFactory.cs b/src/main.lib/Plugins/InstallationPlugins/IISFtp/IISFtpOptionsFactory.cs new file mode 100644 index 0000000..b8deaf9 --- /dev/null +++ b/src/main.lib/Plugins/InstallationPlugins/IISFtp/IISFtpOptionsFactory.cs @@ -0,0 +1,56 @@ +using PKISharp.WACS.Clients.IIS; +using PKISharp.WACS.DomainObjects; +using PKISharp.WACS.Plugins.Base.Factories; +using PKISharp.WACS.Plugins.Interfaces; +using PKISharp.WACS.Plugins.StorePlugins; +using PKISharp.WACS.Services; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace PKISharp.WACS.Plugins.InstallationPlugins +{ + class IISFtpOptionsFactory : InstallationPluginFactory<IISFtp, IISFtpOptions> + { + private readonly IIISClient _iisClient; + private IArgumentsService _arguments; + + public override int Order => 10; + + public IISFtpOptionsFactory(IIISClient iisClient, IArgumentsService arguments) + { + _iisClient = iisClient; + _arguments = arguments; + } + + public override bool CanInstall(IEnumerable<Type> storeTypes) + { + return _iisClient.HasFtpSites && storeTypes.Contains(typeof(CertificateStore)); + } + + public override IISFtpOptions Aquire(Target renewal, IInputService inputService, RunLevel runLevel) + { + var ret = new IISFtpOptions(); + var chosen = inputService.ChooseFromList("Choose ftp site to bind the certificate to", + _iisClient.FtpSites, + x => Choice.Create(x.Id, x.Name, x.Id.ToString())); + ret.SiteId = chosen; + return ret; + } + + public override IISFtpOptions Default(Target renewal) + { + var args = _arguments.GetArguments<IISFtpArguments>(); + var ret = new IISFtpOptions(); + var siteId = args.FtpSiteId; + if (siteId == null) + { + throw new Exception($"Missing parameter --{nameof(args.FtpSiteId).ToLower()}"); + } + // Throws exception when site is not found + var site = _iisClient.GetFtpSite(siteId.Value); + ret.SiteId = site.Id; + return ret; + } + } +} diff --git a/src/main.lib/Plugins/InstallationPlugins/IISWeb/IISWeb.cs b/src/main.lib/Plugins/InstallationPlugins/IISWeb/IISWeb.cs new file mode 100644 index 0000000..f5e8aa8 --- /dev/null +++ b/src/main.lib/Plugins/InstallationPlugins/IISWeb/IISWeb.cs @@ -0,0 +1,82 @@ +using PKISharp.WACS.Clients.IIS; +using PKISharp.WACS.DomainObjects; +using PKISharp.WACS.Plugins.Interfaces; +using PKISharp.WACS.Plugins.StorePlugins; +using PKISharp.WACS.Services; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace PKISharp.WACS.Plugins.InstallationPlugins +{ + internal class IISWeb : IInstallationPlugin + { + private readonly Target _target; + private readonly ILogService _log; + private readonly IIISClient _iisClient; + private readonly IISWebOptions _options; + + public IISWeb(Target target, IISWebOptions options, IIISClient iisClient, ILogService log) + { + _iisClient = iisClient; + _log = log; + _options = options; + _target = target; + } + + void IInstallationPlugin.Install(IEnumerable<IStorePlugin> stores, CertificateInfo newCertificate, CertificateInfo oldCertificate) + { + var bindingOptions = new BindingOptions(). + WithThumbprint(newCertificate.Certificate.GetCertHash()); + + var centralSsl = stores.FirstOrDefault(x => x is CentralSsl); + var certificateStore = stores.FirstOrDefault(x => x is CertificateStore); + + if (centralSsl != null) + { + if (_iisClient.Version.Major < 8) + { + var errorMessage = "Centralized SSL is only supported on IIS8+"; + _log.Error(errorMessage); + throw new InvalidOperationException(errorMessage); + } + else + { + bindingOptions = bindingOptions.WithFlags(SSLFlags.CentralSSL); + } + } + else if (certificateStore != null) + { + bindingOptions = bindingOptions.WithStore(newCertificate.StoreInfo[typeof(CertificateStore)].Path); + } + else + { + // Unknown/unsupported store + var errorMessage = "This installation plugin cannot be used in combination with the store plugin"; + _log.Error(errorMessage); + throw new InvalidOperationException(errorMessage); + } + + // Optionaly overrule the standard IP for new bindings + if (!string.IsNullOrEmpty(_options.NewBindingIp)) + { + bindingOptions = bindingOptions.WithIP(_options.NewBindingIp); + } + + // Optionaly overrule the standard port for new bindings + if (_options.NewBindingPort > 0) + { + bindingOptions = bindingOptions.WithPort(_options.NewBindingPort.Value); + } + + var oldThumb = oldCertificate?.Certificate?.GetCertHash(); + foreach (var part in _target.Parts) + { + _iisClient.AddOrUpdateBindings( + part.Identifiers, + bindingOptions.WithSiteId(_options.SiteId ?? part.SiteId), + oldThumb); + } + } + } +} diff --git a/src/main.lib/Plugins/InstallationPlugins/IISWeb/IISWebArguments.cs b/src/main.lib/Plugins/InstallationPlugins/IISWeb/IISWebArguments.cs new file mode 100644 index 0000000..94f2e1f --- /dev/null +++ b/src/main.lib/Plugins/InstallationPlugins/IISWeb/IISWebArguments.cs @@ -0,0 +1,9 @@ +namespace PKISharp.WACS.Plugins.InstallationPlugins +{ + class IISWebArguments + { + public long? InstallationSiteId { get; set; } + public int SSLPort { get; set; } + public string SSLIPAddress { get; set; } + } +} diff --git a/src/main.lib/Plugins/InstallationPlugins/IISWeb/IISWebArgumentsProvider.cs b/src/main.lib/Plugins/InstallationPlugins/IISWeb/IISWebArgumentsProvider.cs new file mode 100644 index 0000000..0d12454 --- /dev/null +++ b/src/main.lib/Plugins/InstallationPlugins/IISWeb/IISWebArgumentsProvider.cs @@ -0,0 +1,36 @@ +using Fclp; +using PKISharp.WACS.Clients.IIS; +using PKISharp.WACS.Configuration; + +namespace PKISharp.WACS.Plugins.InstallationPlugins +{ + class IISWebArgumentsProvider : BaseArgumentsProvider<IISWebArguments> + { + public override string Name => "IIS Web plugin"; + public override string Group => "Installation"; + public override string Condition => "--installation iis"; + + public override void Configure(FluentCommandLineParser<IISWebArguments> parser) + { + parser.Setup(o => o.InstallationSiteId) + .As("installationsiteid") + .WithDescription("Specify site to install new bindings to. Defaults to the target if that is an IIS site."); + parser.Setup(o => o.SSLPort) + .As("sslport") + .SetDefault(IISClient.DefaultBindingPort) + .WithDescription($"Port number to use for newly created HTTPS bindings. Defaults to {IISClient.DefaultBindingPort}."); + parser.Setup(o => o.SSLIPAddress) + .As("sslipaddress") + .SetDefault(IISClient.DefaultBindingIp) + .WithDescription($"IP address to use for newly created HTTPS bindings. Defaults to {IISClient.DefaultBindingIp}."); + } + + public override bool Active(IISWebArguments current) + { + return current.SSLIPAddress != IISClient.DefaultBindingIp || + current.SSLPort != IISClient.DefaultBindingPort || + current.InstallationSiteId != null; + } + + } +} diff --git a/src/main.lib/Plugins/InstallationPlugins/IISWeb/IISWebOptions.cs b/src/main.lib/Plugins/InstallationPlugins/IISWeb/IISWebOptions.cs new file mode 100644 index 0000000..30a5192 --- /dev/null +++ b/src/main.lib/Plugins/InstallationPlugins/IISWeb/IISWebOptions.cs @@ -0,0 +1,32 @@ +using PKISharp.WACS.Clients.IIS; +using PKISharp.WACS.Plugins.Base; +using PKISharp.WACS.Plugins.Base.Options; + +namespace PKISharp.WACS.Plugins.InstallationPlugins +{ + [Plugin("ea6a5be3-f8de-4d27-a6bd-750b619b2ee2")] + class IISWebOptions : InstallationPluginOptions<IISWeb> + { + public long? SiteId { get; set; } + public string NewBindingIp { get; set; } + public int? NewBindingPort { get; set; } + + public override string Name => "IIS"; + public override string Description => "Create or update https bindings in IIS"; + + public IISWebOptions() { } + public IISWebOptions(IISWebArguments args) + { + var sslIp = args.SSLIPAddress; + if (!string.IsNullOrEmpty(sslIp) && sslIp != IISClient.DefaultBindingIp) + { + NewBindingIp = sslIp; + } + var sslPort = args.SSLPort; + if (sslPort != IISClient.DefaultBindingPort) + { + NewBindingPort = sslPort; + } + } + } +} diff --git a/src/main.lib/Plugins/InstallationPlugins/IISWeb/IISWebOptionsFactory.cs b/src/main.lib/Plugins/InstallationPlugins/IISWeb/IISWebOptionsFactory.cs new file mode 100644 index 0000000..4a0dece --- /dev/null +++ b/src/main.lib/Plugins/InstallationPlugins/IISWeb/IISWebOptionsFactory.cs @@ -0,0 +1,74 @@ +using PKISharp.WACS.Clients.IIS; +using PKISharp.WACS.DomainObjects; +using PKISharp.WACS.Plugins.Base.Factories; +using PKISharp.WACS.Plugins.StorePlugins; +using PKISharp.WACS.Services; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace PKISharp.WACS.Plugins.InstallationPlugins +{ + internal class IISWebOptionsFactory : InstallationPluginFactory<IISWeb, IISWebOptions> + { + public override int Order => 5; + private readonly IIISClient _iisClient; + private IArgumentsService _arguments; + + public IISWebOptionsFactory(IIISClient iisClient, IArgumentsService arguments) + { + _iisClient = iisClient; + _arguments = arguments; + } + + public override bool CanInstall(IEnumerable<Type> storeTypes) + { + return _iisClient.HasWebSites && + (storeTypes.Contains(typeof(CertificateStore)) || + storeTypes.Contains(typeof(CentralSsl))); + } + + public override IISWebOptions Aquire(Target target, IInputService inputService, RunLevel runLevel) + { + var args = _arguments.GetArguments<IISWebArguments>(); + var ret = new IISWebOptions(args); + var ask = true; + if (target.IIS) + { + if (runLevel.HasFlag(RunLevel.Advanced)) + { + ask = inputService.PromptYesNo("Use different site for installation?", false); + } + else + { + ask = false; + } + } + if (ask) + { + var chosen = inputService.ChooseFromList("Choose site to create new bindings", + _iisClient.WebSites, + x => Choice.Create(x.Id, x.Name, x.Id.ToString())); + ret.SiteId = chosen; + } + return ret; + } + + public override IISWebOptions Default(Target target) + { + var args = _arguments.GetArguments<IISWebArguments>(); + var ret = new IISWebOptions(args); + if (args.InstallationSiteId != null) + { + // Throws exception when not found + var site = _iisClient.GetWebSite(args.InstallationSiteId.Value); + ret.SiteId = site.Id; + } + else if (!target.IIS) + { + throw new Exception($"Missing parameter --{nameof(args.InstallationSiteId).ToLower()}"); + } + return ret; + } + } +} diff --git a/src/main.lib/Plugins/InstallationPlugins/Script/Script.cs b/src/main.lib/Plugins/InstallationPlugins/Script/Script.cs new file mode 100644 index 0000000..65147d2 --- /dev/null +++ b/src/main.lib/Plugins/InstallationPlugins/Script/Script.cs @@ -0,0 +1,47 @@ +using PKISharp.WACS.Clients; +using PKISharp.WACS.DomainObjects; +using PKISharp.WACS.Plugins.Interfaces; +using PKISharp.WACS.Services; +using System.Collections.Generic; +using System.Linq; + +namespace PKISharp.WACS.Plugins.InstallationPlugins +{ + internal class Script : ScriptClient, IInstallationPlugin + { + private readonly Renewal _renewal; + private readonly ScriptOptions _options; + + public Script(Renewal renewal, ScriptOptions options, ILogService logService) : base(logService) + { + _options = options; + _renewal = renewal; + } + + public void Install(IEnumerable<IStorePlugin> store, CertificateInfo newCertificate, CertificateInfo oldCertificate) + { + var defaultStoreType = store.First().GetType(); + var defaultStoreInfo = newCertificate.StoreInfo[defaultStoreType]; + var parameters = _options.ScriptParameters ?? ""; + parameters = parameters.Replace("{0}", newCertificate.SubjectName); + parameters = parameters.Replace("{1}", _renewal.PfxPassword?.Value); + parameters = parameters.Replace("{2}", newCertificate.CacheFile?.FullName); + parameters = parameters.Replace("{3}", defaultStoreInfo.Path); + parameters = parameters.Replace("{4}", newCertificate.Certificate.FriendlyName); + parameters = parameters.Replace("{5}", newCertificate.Certificate.Thumbprint); + parameters = parameters.Replace("{6}", newCertificate.CacheFile?.Directory.FullName); + parameters = parameters.Replace("{7}", _renewal.Id); + + parameters = parameters.Replace("{CachePassword}", _renewal.PfxPassword?.Value); + parameters = parameters.Replace("{CacheFile}", newCertificate.CacheFile?.FullName); + parameters = parameters.Replace("{CacheFolder}", newCertificate.CacheFile?.FullName); + parameters = parameters.Replace("{CertCommonName}", newCertificate.SubjectName); + parameters = parameters.Replace("{CertFriendlyName}", newCertificate.Certificate.FriendlyName); + parameters = parameters.Replace("{CertThumbprint}", newCertificate.Certificate.Thumbprint); + parameters = parameters.Replace("{StoreType}", defaultStoreInfo.Name); + parameters = parameters.Replace("{StorePath}", defaultStoreInfo.Path); + parameters = parameters.Replace("{RenewalId}", _renewal.Id); + RunScript(_options.Script, parameters); + } + } +}
\ No newline at end of file diff --git a/src/main.lib/Plugins/InstallationPlugins/Script/ScriptArguments.cs b/src/main.lib/Plugins/InstallationPlugins/Script/ScriptArguments.cs new file mode 100644 index 0000000..f57f794 --- /dev/null +++ b/src/main.lib/Plugins/InstallationPlugins/Script/ScriptArguments.cs @@ -0,0 +1,8 @@ +namespace PKISharp.WACS.Plugins.InstallationPlugins +{ + class ScriptArguments + { + public string Script { get; set; } + public string ScriptParameters { get; set; } + } +} diff --git a/src/main.lib/Plugins/InstallationPlugins/Script/ScriptArgumentsProvider.cs b/src/main.lib/Plugins/InstallationPlugins/Script/ScriptArgumentsProvider.cs new file mode 100644 index 0000000..eb33f21 --- /dev/null +++ b/src/main.lib/Plugins/InstallationPlugins/Script/ScriptArgumentsProvider.cs @@ -0,0 +1,29 @@ +using Fclp; +using PKISharp.WACS.Configuration; +using PKISharp.WACS.Services; + +namespace PKISharp.WACS.Plugins.InstallationPlugins +{ + class ScriptArgumentsProvider : BaseArgumentsProvider<ScriptArguments> + { + public override string Name => "Script plugin"; + public override string Group => "Installation"; + public override string Condition => "--installation script"; + + public override void Configure(FluentCommandLineParser<ScriptArguments> parser) + { + parser.Setup(o => o.Script) + .As("script") + .WithDescription("Path to script file to run after retrieving the certificate. This may be a .exe or .bat. Refer to the Wiki for instructions on how to run .ps1 files."); + parser.Setup(o => o.ScriptParameters) + .As("scriptparameters") + .WithDescription("Parameters for the script to run after retrieving the certificate. Refer to the Wiki for further instructions."); + } + + public override bool Active(ScriptArguments current) + { + return !string.IsNullOrEmpty(current.Script) || + !string.IsNullOrEmpty(current.ScriptParameters); + } + } +} diff --git a/src/main.lib/Plugins/InstallationPlugins/Script/ScriptOptions.cs b/src/main.lib/Plugins/InstallationPlugins/Script/ScriptOptions.cs new file mode 100644 index 0000000..fece33e --- /dev/null +++ b/src/main.lib/Plugins/InstallationPlugins/Script/ScriptOptions.cs @@ -0,0 +1,30 @@ +using PKISharp.WACS.Plugins.Base; +using PKISharp.WACS.Plugins.Base.Options; +using PKISharp.WACS.Services; + +namespace PKISharp.WACS.Plugins.InstallationPlugins +{ + [Plugin("3bb22c70-358d-4251-86bd-11858363d913")] + class ScriptOptions : InstallationPluginOptions<Script> + { + public override string Name => "Script"; + public override string Description => "Start external script or program"; + + public string Script { get; set; } + public string ScriptParameters { get; set; } + + /// <summary> + /// Show details to the user + /// </summary> + /// <param name="input"></param> + public override void Show(IInputService input) + { + base.Show(input); + input.Show("Script", Script, level: 2); + if (!string.IsNullOrEmpty(ScriptParameters)) + { + input.Show("ScriptParameters", ScriptParameters, level: 2); + } + } + } +} diff --git a/src/main.lib/Plugins/InstallationPlugins/Script/ScriptOptionsFactory.cs b/src/main.lib/Plugins/InstallationPlugins/Script/ScriptOptionsFactory.cs new file mode 100644 index 0000000..2a7836a --- /dev/null +++ b/src/main.lib/Plugins/InstallationPlugins/Script/ScriptOptionsFactory.cs @@ -0,0 +1,61 @@ +using PKISharp.WACS.DomainObjects; +using PKISharp.WACS.Extensions; +using PKISharp.WACS.Plugins.Base.Factories; +using PKISharp.WACS.Plugins.StorePlugins; +using PKISharp.WACS.Services; +using System; + +namespace PKISharp.WACS.Plugins.InstallationPlugins +{ + internal class ScriptOptionsFactory : InstallationPluginFactory<Script, ScriptOptions> + { + public override int Order => 100; + private ILogService _log; + private IArgumentsService _arguments; + + public ScriptOptionsFactory(ILogService log, IArgumentsService arguments) + { + _log = log; + _arguments = arguments; + } + + public override ScriptOptions Aquire(Target target, IInputService inputService, RunLevel runLevel) + { + var ret = new ScriptOptions(); + var args = _arguments.GetArguments<ScriptArguments>(); + inputService.Show("Full instructions", "https://github.com/PKISharp/win-acme/wiki/Install-Script"); + do + { + ret.Script = _arguments.TryGetArgument(args.Script, inputService, "Enter the path to the script that you want to run after renewal"); + } + while (!ret.Script.ValidFile(_log)); + + inputService.Show("{CertCommonName}", "Common name (primary domain name)"); + inputService.Show("{CachePassword}", ".pfx password"); + inputService.Show("{CacheFile}", ".pfx full path"); + inputService.Show("{CertFriendlyName}", "Certificate friendly name"); + inputService.Show("{CertThumbprint}", "Certificate thumbprint"); + inputService.Show("{StoreType}", $"Type of store ({CentralSslOptions.PluginName}/{CertificateStoreOptions.PluginName}/{PemFilesOptions.PluginName})"); + inputService.Show("{StorePath}", "Path to the store"); + inputService.Show("{RenewalId}", "Renewal identifier"); + + ret.ScriptParameters = _arguments.TryGetArgument(args.ScriptParameters, inputService, "Enter the parameter format string for the script, e.g. \"--hostname {CertCommonName}\""); + return ret; + } + + public override ScriptOptions Default(Target target) + { + var args = _arguments.GetArguments<ScriptArguments>(); + var ret = new ScriptOptions + { + Script = _arguments.TryGetRequiredArgument(nameof(args.Script), args.Script) + }; + if (!ret.Script.ValidFile(_log)) + { + throw new ArgumentException(nameof(args.Script)); + } + ret.ScriptParameters = args.ScriptParameters; + return ret; + } + } +} |