/// <summary> /// Run the renewal /// </summary> /// <param name="execute"></param> /// <param name="orders"></param> /// <param name="runLevel"></param> /// <returns></returns> private async Task <RenewResult> ExecuteRenewal(ILifetimeScope execute, List <Order> orders, RunLevel runLevel) { var result = new RenewResult(); foreach (var order in orders) { _log.Verbose("Handle order {n}/{m}: {friendly}", orders.IndexOf(order) + 1, orders.Count, order.FriendlyNamePart ?? "Main"); // Create the order details var orderManager = execute.Resolve <OrderManager>(); order.Details = await orderManager.GetOrCreate(order, runLevel); // Create the execution context var context = new ExecutionContext(execute, order, runLevel, result); // Authorize the order (validation) await AuthorizeOrder(context); if (context.Result.Success) { // Execute final steps (CSR, store, install) await ExecuteOrder(context); } } return(result); }
public ExecutionContext(ILifetimeScope scope, Order order, RunLevel runLevel, RenewResult result) { Scope = scope; Order = order; RunLevel = runLevel; Result = result; }
/// <summary> /// Run the renewal /// </summary> /// <param name="execute"></param> /// <param name="orders"></param> /// <param name="runLevel"></param> /// <returns></returns> private async Task <RenewResult> ExecuteRenewal(ILifetimeScope execute, List <Order> orders, RunLevel runLevel) { var result = new RenewResult(); foreach (var order in orders) { _log.Verbose("Handle order {n}/{m}: {friendly}", orders.IndexOf(order) + 1, orders.Count, order.FriendlyNamePart ?? "Main"); // Create the execution context var context = new ExecutionContext(execute, order, runLevel, result); await ExecuteOrder(context, runLevel); } return(result); }
/// <summary> /// Move errors from a validation context up to the renewal result /// </summary> /// <param name="from"></param> /// <param name="to"></param> /// <param name="prefix"></param> private void TransferErrors(ValidationContext from, RenewResult to) { from.ErrorMessages.ForEach(e => to.AddErrorMessage($"[{from.Label}] {e}", from.Success != true)); from.ErrorMessages.Clear(); }
/// <summary> /// Steps to take on succesful (re)authorization /// </summary> /// <param name="target"></param> private RenewResult OnRenewSuccess(ILifetimeScope renewalScope, Renewal renewal, Target target, OrderDetails order, RunLevel runLevel) { RenewResult result = null; try { var certificateService = renewalScope.Resolve <CertificateService>(); var storePlugin = renewalScope.Resolve <IStorePlugin>(); var csrPlugin = renewalScope.Resolve <ICsrPlugin>(); var oldCertificate = renewal.Certificate(storePlugin); var newCertificate = certificateService.RequestCertificate(csrPlugin, renewal, target, order); // Test if a new certificate has been generated if (newCertificate == null) { return(new RenewResult("No certificate generated")); } else { result = new RenewResult(newCertificate); } // Early escape for testing validation only if (renewal.New && runLevel.HasFlag(RunLevel.Test) && !_input.PromptYesNo($"[--test] Do you want to install the certificate?")) { return(new RenewResult("User aborted")); } try { // Check if the newly requested certificate is already in the store, // which might be the case due to the cache mechanism built into the // RequestCertificate function var storedCertificate = storePlugin.FindByThumbprint(newCertificate.Certificate.Thumbprint); if (storedCertificate != null) { // Copy relevant properties _log.Warning("Certificate with thumbprint {thumbprint} is already in the store", newCertificate.Certificate.Thumbprint); newCertificate.Store = storedCertificate.Store; } else { // Save to store storePlugin.Save(newCertificate); } } catch (Exception ex) { _log.Error(ex, "Unable to store certificate"); result.Success = false; result.ErrorMessage = $"Store failed: {ex.Message}"; return(result); } // Run installation plugin(s) try { var steps = renewal.InstallationPluginOptions.Count(); for (var i = 0; i < steps; i++) { var installOptions = renewal.InstallationPluginOptions[i]; var installPlugin = (IInstallationPlugin)renewalScope.Resolve(installOptions.Instance); if (!(installPlugin is INull)) { if (steps > 1) { _log.Information("Installation step {n}/{m}: {name}...", i + 1, steps, installOptions.Name); } else { _log.Information("Installing with {name}...", installOptions.Name); } installPlugin.Install(newCertificate, oldCertificate); } } } catch (Exception ex) { _log.Error(ex, "Unable to install certificate"); result.Success = false; result.ErrorMessage = $"Install failed: {ex.Message}"; } // Delete the old certificate if not forbidden, found and not re-used if (!renewal.StorePluginOptions.KeepExisting && oldCertificate != null && newCertificate.Certificate.Thumbprint != oldCertificate.Certificate.Thumbprint) { try { storePlugin.Delete(oldCertificate); } catch (Exception ex) { _log.Error(ex, "Unable to delete previous certificate"); //result.Success = false; // not a show-stopper, consider the renewal a success result.ErrorMessage = $"Delete failed: {ex.Message}"; } } if (renewal.New && !_arguments.NoTaskScheduler) { if (runLevel.HasFlag(RunLevel.Test) && !_input.PromptYesNo($"[--test] Do you want to automatically renew this certificate?")) { // Early out for test runs return(new RenewResult("User aborted")); } else { // Make sure the Task Scheduler is configured renewalScope.Resolve <TaskSchedulerService>().EnsureTaskScheduler(); } } return(result); } catch (Exception ex) { HandleException(ex); // Result might still contain the Thumbprint of the certificate // that was requested and (partially? installed, which might help // with debugging if (result == null) { result = new RenewResult(ex.Message); } else { result.Success = false; result.ErrorMessage = ex.Message; } } return(result); }
/// <summary> /// Steps to take on succesful (re)authorization /// </summary> /// <param name="target"></param> private static RenewResult OnRenewSuccess(ILifetimeScope renewalScope, ScheduledRenewal renewal) { RenewResult result = null; try { var certificateService = renewalScope.Resolve <CertificateService>(); var storePlugin = renewalScope.Resolve <IStorePlugin>(); var oldCertificate = renewal.Certificate(storePlugin); var newCertificate = certificateService.RequestCertificate(renewal.Binding); // Test if a new certificate has been generated if (newCertificate == null) { return(new RenewResult(new Exception("No certificate generated"))); } else { result = new RenewResult(newCertificate); } // Early escape for testing validation only if (_options.Test && renewal.New && !_input.PromptYesNo($"[--test] Do you want to install the certificate?")) { return(result); } try { // Check if the newly requested certificate is already in the store, // which might be the case due to the cache mechanism built into the // RequestCertificate function var storedCertificate = storePlugin.FindByThumbprint(newCertificate.Certificate.Thumbprint); if (storedCertificate != null) { // Copy relevant properties _log.Warning("Certificate with thumbprint {thumbprint} is already in the store", newCertificate.Certificate.Thumbprint); newCertificate.Store = storedCertificate.Store; } else { // Save to store storePlugin.Save(newCertificate); } } catch (Exception ex) { _log.Error(ex, "Unable to store certificate"); result.Success = false; result.ErrorMessage = $"Store failed: {ex.Message}"; return(result); } // Run installation plugin(s) try { var installFactories = renewalScope.Resolve <List <IInstallationPluginFactory> >(); var steps = installFactories.Count(); for (var i = 0; i < steps; i++) { var installFactory = installFactories[i]; if (!(installFactory is INull)) { var installInstance = (IInstallationPlugin)renewalScope.Resolve(installFactory.Instance); if (steps > 1) { _log.Information("Installation step {n}/{m}: {name}...", i + 1, steps, installFactory.Name); } else { _log.Information("Installing with {name}...", installFactory.Name); } installInstance.Install(newCertificate, oldCertificate); } } } catch (Exception ex) { _log.Error(ex, "Unable to install certificate"); result.Success = false; result.ErrorMessage = $"Install failed: {ex.Message}"; } // Delete the old certificate if not forbidden, found and not re-used if ((!renewal.KeepExisting ?? false) && oldCertificate != null && newCertificate.Certificate.Thumbprint != oldCertificate.Certificate.Thumbprint) { try { storePlugin.Delete(oldCertificate); } catch (Exception ex) { _log.Error(ex, "Unable to delete previous certificate"); //result.Success = false; // not a show-stopper, consider the renewal a success result.ErrorMessage = $"Delete failed: {ex.Message}"; } } // Add or update renewal if ((renewal.New || renewal.Updated) && !_options.NoTaskScheduler && (!_options.Test || _input.PromptYesNo($"[--test] Do you want to automatically renew this certificate?"))) { var taskScheduler = renewalScope.Resolve <TaskSchedulerService>(); taskScheduler.EnsureTaskScheduler(); _renewalService.Save(renewal, result); } // Save renewal to store return(result); } catch (Exception ex) { // Result might still contain the Thumbprint of the certificate // that was requested and (partially? installed, which might help // with debugging HandleException(ex); if (result == null) { result = new RenewResult(ex); } else { result.Success = false; result.ErrorMessage = ex.Message; } } return(result); }
/// <summary> /// Steps to take on succesful (re)authorization /// </summary> /// <param name="target"></param> private RenewResult OnValidationSuccess(ILifetimeScope renewalScope, Renewal renewal, Target target, OrderDetails order, RunLevel runLevel) { RenewResult result = null; try { var certificateService = renewalScope.Resolve <ICertificateService>(); var csrPlugin = target.CsrBytes == null?renewalScope.Resolve <ICsrPlugin>() : null; var oldCertificate = certificateService.CachedInfo(renewal); var newCertificate = certificateService.RequestCertificate(csrPlugin, runLevel, renewal, target, order); // Test if a new certificate has been generated if (newCertificate == null) { return(new RenewResult("No certificate generated")); } else { result = new RenewResult(newCertificate); } // Early escape for testing validation only if (renewal.New && runLevel.HasFlag(RunLevel.Test) && !_input.PromptYesNo($"[--test] Do you want to install the certificate?", true)) { return(new RenewResult("User aborted")); } // Run store plugin(s) var storePluginOptions = new List <StorePluginOptions>(); var storePlugins = new List <IStorePlugin>(); try { var steps = renewal.StorePluginOptions.Count(); for (var i = 0; i < steps; i++) { var storeOptions = renewal.StorePluginOptions[i]; var storePlugin = (IStorePlugin)renewalScope.Resolve(storeOptions.Instance); if (!(storePlugin is INull)) { if (steps > 1) { _log.Information("Store step {n}/{m}: {name}...", i + 1, steps, storeOptions.Name); } else { _log.Information("Store with {name}...", storeOptions.Name); } storePlugin.Save(newCertificate); storePlugins.Add(storePlugin); storePluginOptions.Add(storeOptions); } } } catch (Exception ex) { _log.Error(ex, "Unable to store certificate"); result.Success = false; result.ErrorMessage = $"Store failed: {ex.Message}"; return(result); } // Run installation plugin(s) try { var steps = renewal.InstallationPluginOptions.Count(); for (var i = 0; i < steps; i++) { var installOptions = renewal.InstallationPluginOptions[i]; var installPlugin = (IInstallationPlugin)renewalScope.Resolve(installOptions.Instance); if (!(installPlugin is INull)) { if (steps > 1) { _log.Information("Installation step {n}/{m}: {name}...", i + 1, steps, installOptions.Name); } else { _log.Information("Installing with {name}...", installOptions.Name); } installPlugin.Install(storePlugins, newCertificate, oldCertificate); } } } catch (Exception ex) { _log.Error(ex, "Unable to install certificate"); result.Success = false; result.ErrorMessage = $"Install failed: {ex.Message}"; } // Delete the old certificate if not forbidden, found and not re-used for (var i = 0; i < storePluginOptions.Count; i++) { if (!storePluginOptions[i].KeepExisting && oldCertificate != null && newCertificate.Certificate.Thumbprint != oldCertificate.Certificate.Thumbprint) { try { storePlugins[i].Delete(oldCertificate); } catch (Exception ex) { _log.Error(ex, "Unable to delete previous certificate"); //result.Success = false; // not a show-stopper, consider the renewal a success result.ErrorMessage = $"Delete failed: {ex.Message}"; } } } if (renewal.New && !_args.NoTaskScheduler) { if (runLevel.HasFlag(RunLevel.Test) && !_input.PromptYesNo($"[--test] Do you want to automatically renew this certificate?", true)) { // Early out for test runs return(new RenewResult("User aborted")); } else { // Make sure the Task Scheduler is configured renewalScope.Resolve <TaskSchedulerService>().EnsureTaskScheduler(runLevel); } } return(result); } catch (Exception ex) { HandleException(ex); while (ex.InnerException != null) { ex = ex.InnerException; } // Result might still contain the Thumbprint of the certificate // that was requested and (partially? installed, which might help // with debugging if (result == null) { result = new RenewResult(ex.Message); } else { result.Success = false; result.ErrorMessage = ex.Message; } } return(result); }
/// <summary> /// Run the renewal /// </summary> /// <param name="execute"></param> /// <param name="orders"></param> /// <param name="runLevel"></param> /// <returns></returns> private async Task <RenewResult> HandleOrders(ILifetimeScope execute, Renewal renewal, List <Order> orders, RunLevel runLevel) { // Build context var result = new RenewResult(); var orderContexts = orders.Select(order => new OrderContext(execute, order, runLevel, result)).ToList(); // Check if renewal is needed at the root level var mainDue = !ShouldRunRenewal(renewal, runLevel); // Check individual orders foreach (var o in orderContexts) { o.ShouldRun = runLevel.HasFlag(RunLevel.ForceRenew) || _dueDate.ShouldRun(o.Order); } if (!mainDue) { // If renewal is not needed at the root level // it may be needed at the order level due to // change in target. Here we check this. if (!orderContexts.Any(x => x.ShouldRun)) { return(Abort(renewal)); } } // Only process orders that are due. In the normal // case when using static due dates this will be all // the orders. But when using the random due dates, // this could only be a part of them. if (!runLevel.HasFlag(RunLevel.IgnoreCache) && !renewal.New && !renewal.Updated) { orderContexts = orderContexts.Where(x => x.ShouldRun).ToList(); } if (!orderContexts.Any()) { _log.Debug("None of the orders are currently due to run", orderContexts.Count, orders.Count); return(Abort(renewal)); } if (!renewal.New && !runLevel.HasFlag(RunLevel.ForceRenew)) { _log.Information(LogType.All, "Renewing {renewal}", renewal.LastFriendlyName); } if (orders.Count > orderContexts.Count) { _log.Information("{n} of {m} orders are due to run", orderContexts.Count, orders.Count); } // If at this point we haven't retured already with an error/abort // actually execute the renewal // Run the pre-execution script, e.g. to re-configure // local firewall rules, since now it's (almost) sure // that we're going to do something. Actually we may // still be able to read all certificates from cache, // but that's the exception rather than the rule. var preScript = _settings.Execution?.DefaultPreExecutionScript; var scriptClient = execute.Resolve <ScriptClient>(); if (!string.IsNullOrWhiteSpace(preScript)) { await scriptClient.RunScript(preScript, $"{renewal.Id}"); } // Get the certificates from cache or server await ExecuteOrders(orderContexts, runLevel); // Handle all the store/install steps await ProcessOrders(orderContexts); // Run the post-execution script. Note that this is different // from the script installation plugin, which is handled // in the previous step. This is only meant to undo any // (firewall?) changes made by the pre-execution script. var postScript = _settings.Execution?.DefaultPostExecutionScript; if (!string.IsNullOrWhiteSpace(postScript)) { await scriptClient.RunScript(postScript, $"{renewal.Id}"); } // Return final result return(result); }