public override async Task CreateRecord(string recordName, string token) { _input.CreateSpace(); _input.Show("Domain", _identifier); _input.Show("Record", recordName); _input.Show("Type", "TXT"); _input.Show("Content", $"\"{token}\""); _input.Show("Note", "Some DNS managers add quotes automatically. A single set is needed."); await _input.Wait("Please press <Enter> after you've created and verified the record"); // Pre-pre-validate, allowing the manual user to correct mistakes while (true) { if (await PreValidate(0)) { break; } else { var retry = await _input.PromptYesNo( "The correct record is not yet found by the local resolver. " + "Check your configuration and/or wait for the name servers to " + "synchronize and press <Enter> to try again. Answer 'N' to " + "try ACME validation anyway.", true); if (!retry) { break; } } } }
public override async Task <bool> CreateRecord(DnsValidationRecord record) { _input.CreateSpace(); _input.Show("Domain", record.Context.Identifier); _input.Show("Record", record.Authority.Domain); _input.Show("Type", "TXT"); _input.Show("Content", $"\"{record.Value}\""); _input.Show("Note", "Some DNS managers add quotes automatically. A single set is needed."); if (!await _input.Wait("Please press <Enter> after you've created and verified the record")) { _log.Warning("User aborted"); return(false); } if (!_settings.Validation.PreValidateDns) { return(true); } // Pre-pre-validate, allowing the manual user to correct mistakes while (true) { if (await PreValidate(record)) { return(true); } else { _input.CreateSpace(); _input.Show(null, value: "The correct record has not yet been found by the local resolver. That means it's likely the validation attempt will fail, or your DNS provider needs a little more time to publish and synchronize the changes."); var options = new List <Choice <bool?> > { Choice.Create <bool?>(null, "Retry check"), Choice.Create <bool?>(true, "Ignore and continue"), Choice.Create <bool?>(false, "Abort") }; var chosen = await _input.ChooseFromMenu("How would you like to proceed?", options); if (chosen != null) { return(chosen.Value); } } } }
/// <summary> /// Interactive input of a search pattern /// </summary> /// <param name="input"></param> /// <param name="options"></param> /// <returns></returns> async Task InputPattern(IInputService input, IISOptions options) { input.CreateSpace(); input.Show(null, IISArgumentsProvider.PatternExamples); string raw; do { raw = await input.RequestString("Pattern"); } while (!ParsePattern(raw, options)); }
public override async Task <ScriptOptions> Aquire(Target target, IInputService inputService, RunLevel runLevel) { var ret = new ScriptOptions { Script = await Script.Interactive(inputService, "File").GetValue(), }; inputService.CreateSpace(); 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 (e.g. {CentralSslOptions.PluginName}, {CertificateStoreOptions.PluginName}, {PemFilesOptions.PluginName}, ...)"); inputService.Show("{StorePath}", "Path to the store"); inputService.Show("{RenewalId}", "Renewal identifier"); inputService.Show("{OldCertCommonName}", "Common name (primary domain name) of the previously issued certificate"); inputService.Show("{OldCertFriendlyName}", "Friendly name of the previously issued certificate"); inputService.Show("{OldCertThumbprint}", "Thumbprint of the previously issued certificate"); inputService.CreateSpace(); ret.ScriptParameters = await Parameters.Interactive(inputService, "Parameters").GetValue(); return(ret); }
public override async Task <ScriptOptions> Aquire(Target target, IInputService inputService, RunLevel runLevel) { inputService.CreateSpace(); inputService.Show("Full instructions", "https://win-acme.com/reference/plugins/installation/script"); inputService.CreateSpace(); 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"); inputService.Show("{OldCertCommonName}", "Common name (primary domain name) of the previously issued certificate"); inputService.Show("{OldCertFriendlyName}", "Friendly name of the previously issued certificate"); inputService.Show("{OldCertThumbprint}", "Thumbprint of the previously issued certificate"); inputService.CreateSpace(); return(new ScriptOptions { Script = await Script.Interactive(inputService, "File").GetValue(), ScriptParameters = await Parameters.Interactive(inputService, "Parameters").GetValue() }); }
public override async Task <bool> CreateRecord(DnsValidationRecord record) { _input.CreateSpace(); _input.Show("Domain", record.Context.Identifier); _input.Show("Record", record.Authority.Domain); _input.Show("Type", "TXT"); _input.Show("Content", $"\"{record.Value}\""); _input.Show("Note", "Some DNS managers add quotes automatically. A single set is needed."); if (!await _input.Wait("Please press <Enter> after you've created and verified the record")) { _log.Warning("User aborted"); return(false); } // Pre-pre-validate, allowing the manual user to correct mistakes while (true) { if (await PreValidate(record)) { return(true); } else { var retry = await _input.PromptYesNo( "The correct record is not yet found by the local resolver. " + "Check your configuration and/or wait for the name servers to " + "synchronize and press <Enter> to try again. Answer 'N' to " + "try ACME validation anyway.", true); if (!retry) { return(false); } } } }
/// <summary> /// If renewal is already Scheduled, replace it with the new options /// </summary> /// <param name="target"></param> /// <returns></returns> private async Task <Renewal> CreateRenewal(Renewal temp, RunLevel runLevel) { // First check by id var existing = _renewalStore.FindByArguments(temp.Id, null).FirstOrDefault(); // If Id has been specified, we don't consider the Friendlyname anymore // So specifying --id becomes a way to create duplicate certificates // with the same --friendlyname in unattended mode. if (existing == null && string.IsNullOrEmpty(_args.Id)) { existing = _renewalStore.FindByArguments(null, temp.LastFriendlyName?.EscapePattern()).FirstOrDefault(); } // This will be a completely new renewal, no further processing needed if (existing == null) { return(temp); } // Match found with existing certificate, determine if we want to overwrite // it or create it side by side with the current one. if (runLevel.HasFlag(RunLevel.Interactive) && (temp.Id != existing.Id) && temp.New) { _input.CreateSpace(); _input.Show("Existing renewal", existing.ToString(_dueDate, _input)); if (!await _input.PromptYesNo($"Overwrite settings?", true)) { return(temp); } } // Move settings from temporary renewal over to // the pre-existing one that we are overwriting _log.Warning("Overwriting previously created renewal"); existing.Updated = true; existing.TargetPluginOptions = temp.TargetPluginOptions; existing.OrderPluginOptions = temp.OrderPluginOptions; existing.CsrPluginOptions = temp.CsrPluginOptions; existing.StorePluginOptions = temp.StorePluginOptions; existing.ValidationPluginOptions = temp.ValidationPluginOptions; existing.InstallationPluginOptions = temp.InstallationPluginOptions; return(existing); }
public async Task Import(RunLevel runLevel) { if (!_legacyRenewal.Renewals.Any()) { _log.Warning("No legacy renewals found"); } _log.Information("Legacy renewals {x}", _legacyRenewal.Renewals.Count().ToString()); _log.Information("Current renewals {x}", _currentRenewal.Renewals.Count().ToString()); _log.Information("Step {x}/3: convert renewals", 1); foreach (var legacyRenewal in _legacyRenewal.Renewals) { var converted = Convert(legacyRenewal); _currentRenewal.Import(converted); } _log.Information("Step {x}/3: create new scheduled task", 2); await _currentTaskScheduler.EnsureTaskScheduler(runLevel | RunLevel.Import, true); _legacyTaskScheduler.StopTaskScheduler(); _log.Information("Step {x}/3: ensure ACMEv2 account", 3); await _acmeClient.GetAccount(); var listCommand = "--list"; var renewCommand = "--renew"; if (runLevel.HasFlag(RunLevel.Interactive)) { listCommand = "Manage renewals"; renewCommand = "Run"; } _input.CreateSpace(); _input.Show(null, value: $"The renewals have now been imported into this new version " + "of the program. Nothing else will happen until new scheduled task is " + "first run *or* you trigger them manually. It is highly recommended " + $"to review the imported items with '{listCommand}' and to monitor the " + $"results of the first execution with '{renewCommand}'."); }
/// <summary> /// Single round of aquiring settings /// </summary> /// <param name="input"></param> /// <param name="allBindings"></param> /// <param name="visibleBindings"></param> /// <param name="allSites"></param> /// <param name="visibleSites"></param> /// <param name="runLevel"></param> /// <returns></returns> private async Task<IISOptions?> TryAquireSettings( IInputService input, List<IISHelper.IISBindingOption> allBindings, List<IISHelper.IISBindingOption> visibleBindings, List<IISHelper.IISSiteOption> allSites, List<IISHelper.IISSiteOption> visibleSites, RunLevel runLevel) { input.CreateSpace(); input.Show(null, "Please select which website(s) should be scanned for host names. " + "You may input one or more site identifiers (comma-separated) to filter by those sites, " + "or alternatively leave the input empty to scan *all* websites."); var options = new IISOptions(); await input.WritePagedList( visibleSites.Select(x => Choice.Create( item: x, description: $"{x.Name} ({x.Hosts.Count()} binding{(x.Hosts.Count() == 1 ? "" : "s")})", command: x.Id.ToString(), color: x.Https ? ConsoleColor.DarkGray : (ConsoleColor?)null))); var raw = await input.RequestString("Site identifier(s) or <Enter> to choose all"); if (!ParseSiteOptions(raw, allSites, options)) { return null; } var filtered = _iisHelper.FilterBindings(visibleBindings, options); await ListBindings(input, filtered); input.CreateSpace(); input.Show(null, "Listed above are the bindings found on the selected site(s). By default all of " + "them will be included, but you may either pick specific ones by typing the host names " + "or identifiers (comma-separated) or filter them using one of the options from the " + "menu."); var askExclude = true; var filters = new List<Choice<Func<Task>>> { Choice.Create<Func<Task>>(() => { return InputPattern(input, options); }, "Pick bindings based on a search pattern", command: "P"), Choice.Create<Func<Task>>(() => { askExclude = false; return Task.CompletedTask; }, "Pick *all* bindings", @default: true, command: "A") }; if (runLevel.HasFlag(RunLevel.Advanced)) { filters.Insert(1, Choice.Create<Func<Task>>(() => { askExclude = true; return InputRegex(input, options); }, "Pick bindings based on a regular expression", command: "R")); } // Handle undefined input Choice<Func<Task>> processUnkown(string unknown) { return Choice.Create<Func<Task>>(() => { askExclude = false; return ProcessInputHosts( unknown, allBindings, filtered, options, () => options.IncludeHosts, x => options.IncludeHosts = x); }); } var chosen = await input.ChooseFromMenu( "Binding identifiers(s) or menu option", filters, processUnkown); await chosen.Invoke(); filtered = _iisHelper.FilterBindings(allBindings, options); // Exclude specific bindings if (askExclude && filtered.Count > 1 && runLevel.HasFlag(RunLevel.Advanced)) { await ListBindings(input, filtered); input.CreateSpace(); input.Show(null, "The listed bindings match your current filter settings. " + "If you wish to exclude one or more of them from the certificate, please " + "input those bindings now. Press <Enter> to include all listed bindings."); await InputHosts("Exclude bindings", input, allBindings, filtered, options, () => options.ExcludeHosts, x => options.ExcludeHosts = x); if (options.ExcludeHosts != null) { filtered = _iisHelper.FilterBindings(allBindings, options); } } // Now the common name if (filtered.Select(x => x.HostUnicode).Distinct().Count() > 1) { await InputCommonName(input, filtered, options); } return options; }
/// <summary> /// Setup a new ACME account /// </summary> /// <param name="client"></param> /// <returns></returns> private async Task SetupAccount(AcmeProtocolClient client) { // Accept the terms of service, if defined by the server try { var(_, filename, content) = await client.GetTermsOfServiceAsync(); _log.Verbose("Terms of service downloaded"); if (!string.IsNullOrEmpty(filename)) { if (!await AcceptTos(filename, content)) { return; } } } catch (Exception ex) { _log.Error(ex, "Error getting terms of service"); } var contacts = default(string[]); var eabKid = _accountArguments.EabKeyIdentifier; var eabKey = _accountArguments.EabKey; var eabAlg = _accountArguments.EabAlgorithm ?? "HS256"; var eabFlow = client.Directory?.Meta?.ExternalAccountRequired == "true"; var zeroSslFlow = _settings.BaseUri.Host.Contains("zerossl.com"); // Warn about unneeded EAB if (!eabFlow && !string.IsNullOrWhiteSpace(eabKid)) { eabFlow = true; _input.CreateSpace(); _input.Show(null, "You have provided an external account binding key, even though " + "the server does not indicate that this is required. We will attempt to register " + "using this key anyway."); } if (zeroSslFlow) { async Task emailRegistration() { var registration = await GetContacts(allowMultiple : false, prefix : ""); var eab = await _zeroSsl.Register(registration.FirstOrDefault() ?? ""); if (eab != null) { eabKid = eab.Kid; eabKey = eab.Hmac; } else { _log.Error("Unable to retrieve EAB credentials using the provided email address"); } } async Task apiKeyRegistration() { var accessKey = await _input.ReadPassword("API access key"); var eab = await _zeroSsl.Obtain(accessKey ?? ""); if (eab != null) { eabKid = eab.Kid; eabKey = eab.Hmac; } else { _log.Error("Unable to retrieve EAB credentials using the provided API access key"); } } if (!string.IsNullOrWhiteSpace(_accountArguments.EmailAddress)) { await emailRegistration(); } else { var instruction = "ZeroSsl can be used either by setting up a new " + "account using your email address or by connecting it to your existing " + "account using the API access key or pre-generated EAB credentials, which can " + "be obtained from the Developer section of the dashboard."; _input.CreateSpace(); _input.Show(null, instruction); var chosen = await _input.ChooseFromMenu( "How would you like to create the account?", new List <Choice <Func <Task> > >() { Choice.Create(apiKeyRegistration, "API access key"), Choice.Create(emailRegistration, "Email address"), Choice.Create <Func <Task> >(() => Task.CompletedTask, "Input EAB credentials directly") }); await chosen.Invoke(); } } if (eabFlow) { if (string.IsNullOrWhiteSpace(eabKid)) { var instruction = "This ACME endpoint requires an external account. " + "You will need to provide a key identifier and a key to proceed. " + "Please refer to the providers instructions on how to obtain these."; _input.CreateSpace(); _input.Show(null, instruction); eabKid = await _input.RequestString("Key identifier"); } if (string.IsNullOrWhiteSpace(eabKey)) { eabKey = await _input.ReadPassword("Key (base64url encoded)"); } contacts = await GetContacts(runLevel : RunLevel.Unattended); } else { contacts = await GetContacts(); } var signer = _accountManager.DefaultSigner(); try { await CreateAccount(client, signer, contacts, eabAlg, eabKid, eabKey); } catch (AcmeProtocolException apex) { // Some non-ACME compliant server may not support ES256 or other // algorithms, so attempt fallback to RS256 if (apex.ProblemType == acme.ProblemType.BadSignatureAlgorithm && signer.KeyType != "RS256") { signer = _accountManager.NewSigner("RS256"); await CreateAccount(client, signer, contacts, eabAlg, eabKid, eabKey); } else { throw; } } catch (Exception ex) { _log.Error(ex, "Error creating account"); throw; } }
/// <summary> /// Renewal management mode /// </summary> /// <returns></returns> internal async Task ManageRenewals() { IEnumerable <Renewal> originalSelection = _renewalStore.Renewals.OrderBy(x => x.LastFriendlyName); var selectedRenewals = originalSelection; var quit = false; var displayAll = false; do { var all = selectedRenewals.Count() == originalSelection.Count(); var none = !selectedRenewals.Any(); var totalLabel = originalSelection.Count() != 1 ? "renewals" : "renewal"; var renewalSelectedLabel = selectedRenewals.Count() != 1 ? "renewals" : "renewal"; var selectionLabel = all ? selectedRenewals.Count() == 1 ? "the renewal" : "*all* renewals" : none ? "no renewals" : $"{selectedRenewals.Count()} of {originalSelection.Count()} {totalLabel}"; _input.CreateSpace(); _input.Show(null, "Welcome to the renewal manager. Actions selected in the menu below will " + "be applied to the following list of renewals. You may filter the list to target " + "your action at a more specific set of renewals, or sort it to make it easier to " + "find what you're looking for."); var displayRenewals = selectedRenewals; var displayLimited = !displayAll && selectedRenewals.Count() >= _settings.UI.PageSize; var displayHidden = 0; var displayHiddenLabel = ""; if (displayLimited) { displayRenewals = displayRenewals.Take(_settings.UI.PageSize - 1); displayHidden = selectedRenewals.Count() - displayRenewals.Count(); displayHiddenLabel = displayHidden != 1 ? "renewals" : "renewal"; } var choices = displayRenewals.Select(x => Choice.Create <Renewal?>(x, description: x.ToString(_input), color: x.History.LastOrDefault()?.Success ?? false ? x.IsDue() ? ConsoleColor.DarkYellow : ConsoleColor.Green : ConsoleColor.Red)).ToList(); if (displayLimited) { choices.Add(Choice.Create <Renewal?>(null, command: "More", description: $"{displayHidden} additional {displayHiddenLabel} selected but currently not displayed")); } await _input.WritePagedList(choices); displayAll = false; var options = new List <Choice <Func <Task> > >(); if (displayLimited) { options.Add( Choice.Create <Func <Task> >( () => { displayAll = true; return(Task.CompletedTask); }, "List all selected renewals", "A")); } options.Add( Choice.Create <Func <Task> >( async() => { quit = true; await EditRenewal(selectedRenewals.First()); }, "Edit renewal", "E", @disabled: (selectedRenewals.Count() != 1, none ? "No renewals selected." : "Multiple renewals selected."))); if (selectedRenewals.Count() > 1) { options.Add( Choice.Create <Func <Task> >( async() => selectedRenewals = await FilterRenewalsMenu(selectedRenewals), all ? "Apply filter" : "Apply additional filter", "F", @disabled: (selectedRenewals.Count() < 2, "Not enough renewals to filter."))); options.Add( Choice.Create <Func <Task> >( async() => selectedRenewals = await SortRenewalsMenu(selectedRenewals), "Sort renewals", "S", @disabled: (selectedRenewals.Count() < 2, "Not enough renewals to sort."))); } if (!all) { options.Add( Choice.Create <Func <Task> >( () => { selectedRenewals = originalSelection; return(Task.CompletedTask); }, "Reset sorting and filtering", "X", @disabled: (all, "No filters have been applied yet."))); } options.Add( Choice.Create <Func <Task> >( async() => { foreach (var renewal in selectedRenewals) { var index = selectedRenewals.ToList().IndexOf(renewal) + 1; _log.Information("Details for renewal {n}/{m}", index, selectedRenewals.Count()); await ShowRenewal(renewal); var cont = false; if (index != selectedRenewals.Count()) { cont = await _input.Wait("Press <Enter> to continue or <Esc> to abort"); if (!cont) { break; } } else { await _input.Wait(); } } }, $"Show details for {selectionLabel}", "D", @disabled: (none, "No renewals selected."))); options.Add( Choice.Create <Func <Task> >( async() => { WarnAboutRenewalArguments(); foreach (var renewal in selectedRenewals) { var runLevel = RunLevel.Interactive | RunLevel.ForceRenew; if (_args.Force) { runLevel |= RunLevel.IgnoreCache; } await ProcessRenewal(renewal, runLevel); } }, $"Run {selectionLabel}", "R", @disabled: (none, "No renewals selected."))); options.Add( Choice.Create <Func <Task> >( async() => selectedRenewals = await Analyze(selectedRenewals), $"Analyze duplicates for {selectionLabel}", "U", @disabled: (none, "No renewals selected."))); options.Add( Choice.Create <Func <Task> >( async() => { var confirm = await _input.PromptYesNo($"Are you sure you want to cancel {selectedRenewals.Count()} currently selected {renewalSelectedLabel}?", false); if (confirm) { foreach (var renewal in selectedRenewals) { _renewalStore.Cancel(renewal); } ; originalSelection = _renewalStore.Renewals.OrderBy(x => x.LastFriendlyName); selectedRenewals = originalSelection; } }, $"Cancel {selectionLabel}", "C", @disabled: (none, "No renewals selected."))); options.Add( Choice.Create <Func <Task> >( async() => { var confirm = await _input.PromptYesNo($"Are you sure you want to revoke the most recently issued certificate for {selectedRenewals.Count()} currently selected {renewalSelectedLabel}? This should only be done in case of a (suspected) security breach. Cancel the {renewalSelectedLabel} if you simply don't need the certificates anymore.", false); if (confirm) { await RevokeCertificates(selectedRenewals); } }, $"Revoke certificate(s) for {selectionLabel}", "V", @disabled: (none, "No renewals selected."))); options.Add( Choice.Create <Func <Task> >( () => { quit = true; return(Task.CompletedTask); }, "Back", "Q", @default: !originalSelection.Any())); if (selectedRenewals.Count() > 1) { _input.CreateSpace(); _input.Show(null, $"Currently selected {selectedRenewals.Count()} of {originalSelection.Count()} {totalLabel}"); } var chosen = await _input.ChooseFromMenu( "Choose an action or type numbers to select renewals", options, (string unexpected) => Choice.Create <Func <Task> >( async() => selectedRenewals = await FilterRenewalsById(selectedRenewals, unexpected))); await chosen.Invoke(); }while (!quit); }
/// <summary> /// Determine if the renewal should be executes /// </summary> /// <param name="renewal"></param> /// <param name="runLevel"></param> /// <returns></returns> public async Task <RenewResult> HandleRenewal(Renewal renewal, RunLevel runLevel) { _input.CreateSpace(); _log.Reset(); using var ts = _scopeBuilder.Target(_container, renewal, runLevel); using var es = _scopeBuilder.Execution(ts, renewal, runLevel); // Generate the target var targetPlugin = es.Resolve <ITargetPlugin>(); var(disabled, disabledReason) = targetPlugin.Disabled; if (disabled) { return(new RenewResult($"Target plugin is not available. {disabledReason}")); } var target = await targetPlugin.Generate(); if (target is INull) { return(new RenewResult($"Target plugin did not generate a target")); } if (!target.IsValid(_log)) { return(new RenewResult($"Target plugin generated an invalid target")); } // Check if our validation plugin is (still) up to the task var validationPlugin = es.Resolve <IValidationPluginOptionsFactory>(); if (!validationPlugin.CanValidate(target)) { return(new RenewResult($"Validation plugin is unable to validate the target. A wildcard host was introduced into a HTTP validated renewal.")); } // Create one or more orders based on the target var orderPlugin = es.Resolve <IOrderPlugin>(); var orders = orderPlugin.Split(renewal, target); if (orders == null || orders.Count() == 0) { return(new RenewResult("Order plugin failed to create order(s)")); } _log.Verbose("Targeted convert into {n} order(s)", orders.Count()); // Check if renewal is needed if (!runLevel.HasFlag(RunLevel.ForceRenew) && !renewal.Updated) { _log.Verbose("Checking {renewal}", renewal.LastFriendlyName); if (!renewal.IsDue()) { var cs = es.Resolve <ICertificateService>(); var abort = true; foreach (var order in orders) { var cache = cs.CachedInfo(order); if (cache == null && !renewal.New) { _log.Information(LogType.All, "Renewal for {renewal} running prematurely due to detected target change", renewal.LastFriendlyName); abort = false; break; } } if (abort) { _log.Information("Renewal for {renewal} is due after {date}", renewal.LastFriendlyName, renewal.GetDueDate()); return(new RenewResult() { Abort = true }); } } else if (!renewal.New) { _log.Information(LogType.All, "Renewing certificate for {renewal}", renewal.LastFriendlyName); } } else if (runLevel.HasFlag(RunLevel.ForceRenew)) { _log.Information(LogType.All, "Force renewing certificate for {renewal}", renewal.LastFriendlyName); } // If at this point we haven't retured already with an error/abort // actually execute the renewal var result = await ExecuteRenewal(es, orders.ToList(), runLevel); // Configure task scheduler if (result.Success && !result.Abort) { if ((renewal.New || renewal.Updated) && !_args.NoTaskScheduler) { if (runLevel.HasFlag(RunLevel.Test) && !await _input.PromptYesNo($"[--test] Do you want to automatically renew with these settings?", true)) { // Early out for test runs result.Abort = true; return(result); } else { // Make sure the Task Scheduler is configured await es.Resolve <TaskSchedulerService>().EnsureTaskScheduler(runLevel, false); } } } return(result); }
private async Task <AccountDetails?> LoadAccount(AcmeProtocolClient client, IJwsTool?signer) { _log.Verbose("Loading ACME account"); AccountDetails?account = null; if (File.Exists(AccountPath)) { if (signer != null) { _log.Debug("Loading account information from {registrationPath}", AccountPath); account = JsonConvert.DeserializeObject <AccountDetails>(File.ReadAllText(AccountPath)); client.Account = account; // Maybe we should update the account details // on every start of the program to figure out // if it hasn't been suspended or cancelled? // UpdateAccount(); } else { _log.Error("Account found but no valid signer could be loaded"); } } else { _log.Verbose("No account found at {path}, creating new one", AccountPath); try { var(_, filename, content) = await client.GetTermsOfServiceAsync(); _log.Verbose("Terms of service downloaded"); if (!string.IsNullOrEmpty(filename)) { if (!await AcceptTos(filename, content)) { return(null); } } } catch (Exception ex) { _log.Error(ex, "Error getting terms of service"); } var contacts = default(string[]); var externalAccount = default(ExternalAccountBinding); var kid = _accountArguments.EabKeyIdentifier; var key = _accountArguments.EabKey; var alg = _accountArguments.EabAlgorithm ?? "HS256"; var eabFlow = client.Directory?.Meta?.ExternalAccountRequired == "true"; if (eabFlow) { _input.CreateSpace(); _input.Show(null, "This ACME endpoint requires an external account. You will " + "need to provide a key identifier and a key to proceed. Please refer to the " + "providers instructions on how to obtain these."); } else if (!string.IsNullOrWhiteSpace(kid)) { eabFlow = true; _input.CreateSpace(); _input.Show(null, "You have provided external account binding key, but the server" + "claims that is not required. We will attempt to register using this key anyway."); } if (eabFlow) { if (string.IsNullOrWhiteSpace(kid)) { kid = await _input.RequestString("Key identifier"); } if (string.IsNullOrWhiteSpace(key)) { key = await _input.ReadPassword("Key (base64url encoded)"); } externalAccount = new ExternalAccountBinding( alg, JsonConvert.SerializeObject(client.Signer.ExportJwk(), Formatting.None), kid, key ?? "", client.Directory?.NewAccount ?? ""); } else { contacts = await GetContacts(); } try { account = await client.CreateAccountAsync( contacts, termsOfServiceAgreed : true, externalAccountBinding : externalAccount?.Payload() ?? null); } catch (Exception ex) { _log.Error(ex, "Error creating account"); } if (account != null) { try { _log.Debug("Saving account"); var accountKey = new AccountSigner { KeyType = client.Signer.JwsAlg, KeyExport = client.Signer.Export(), }; AccountSigner = accountKey; await File.WriteAllTextAsync(AccountPath, JsonConvert.SerializeObject(account)); } catch (Exception ex) { _log.Error(ex, "Error saving account"); account = null; } } } return(account); }
private async Task <AccountDetails?> LoadAccount(AcmeProtocolClient client, IJwsTool?signer) { _log.Verbose("Loading ACME account"); AccountDetails?account = null; if (File.Exists(AccountPath)) { if (signer != null) { _log.Debug("Loading account information from {registrationPath}", AccountPath); account = JsonConvert.DeserializeObject <AccountDetails>(File.ReadAllText(AccountPath)); client.Account = account; // Maybe we should update the account details // on every start of the program to figure out // if it hasn't been suspended or cancelled? // UpdateAccount(); } else { _log.Error("Account found but no valid signer could be loaded"); } } else { _log.Verbose("No account found at {path}, creating new one", AccountPath); try { var(_, filename, content) = await client.GetTermsOfServiceAsync(); _log.Verbose("Terms of service downloaded"); if (!string.IsNullOrEmpty(filename)) { if (!await AcceptTos(filename, content)) { return(null); } } } catch (Exception ex) { _log.Error(ex, "Error getting terms of service"); } var contacts = default(string[]); var externalAccount = default(ExternalAccountBinding); var kid = _accountArguments.EabKeyIdentifier; var key = _accountArguments.EabKey; var alg = _accountArguments.EabAlgorithm ?? "HS256"; var eabFlow = client.Directory?.Meta?.ExternalAccountRequired == "true"; var zeroSslFlow = _settings.BaseUri.Host.Contains("zerossl.com"); // Warn about unneeded EAB if (!eabFlow && !string.IsNullOrWhiteSpace(kid)) { eabFlow = true; _input.CreateSpace(); _input.Show(null, "You have provided an external account binding key, even though " + "the server does not indicate that this is required. We will attempt to register " + "using this key anyway."); } if (zeroSslFlow) { async Task emailRegistration() { var registration = await GetContacts(allowMultiple : false, prefix : ""); var eab = await _zeroSsl.Register(registration.FirstOrDefault() ?? ""); if (eab != null) { kid = eab.Kid; key = eab.Hmac; } else { _log.Error("Unable to retrieve EAB credentials using the provided email address"); } } async Task apiKeyRegistration() { var accessKey = await _input.ReadPassword("API access key"); var eab = await _zeroSsl.Obtain(accessKey ?? ""); if (eab != null) { kid = eab.Kid; key = eab.Hmac; } else { _log.Error("Unable to retrieve EAB credentials using the provided API access key"); } } if (!string.IsNullOrWhiteSpace(_accountArguments.EmailAddress)) { await emailRegistration(); } else { var instruction = "ZeroSsl can be used either by setting up a new " + "account using your email address or by connecting it to your existing " + "account using the API access key or pre-generated EAB credentials, which can " + "be obtained from the Developer section of the dashboard."; _input.CreateSpace(); _input.Show(null, instruction); var chosen = await _input.ChooseFromMenu( "How would you like to create the account?", new List <Choice <Func <Task> > >() { Choice.Create((Func <Task>)apiKeyRegistration, "API access key"), Choice.Create((Func <Task>)emailRegistration, "Email address"), Choice.Create <Func <Task> >(() => Task.CompletedTask, "Input EAB credentials directly") }); await chosen.Invoke(); } } if (eabFlow) { if (string.IsNullOrWhiteSpace(kid)) { var instruction = "This ACME endpoint requires an external account. " + "You will need to provide a key identifier and a key to proceed. " + "Please refer to the providers instructions on how to obtain these."; _input.CreateSpace(); _input.Show(null, instruction); kid = await _input.RequestString("Key identifier"); } if (string.IsNullOrWhiteSpace(key)) { key = await _input.ReadPassword("Key (base64url encoded)"); } externalAccount = new ExternalAccountBinding( alg, JsonConvert.SerializeObject( client.Signer.ExportJwk(), Formatting.None), kid, key ?? "", client.Directory?.NewAccount ?? ""); } else { contacts = await GetContacts(); } try { account = await client.CreateAccountAsync( contacts, termsOfServiceAgreed : true, externalAccountBinding : externalAccount?.Payload() ?? null); } catch (Exception ex) { _log.Error(ex, "Error creating account"); } if (account != null) { try { _log.Debug("Saving account"); var accountKey = new AccountSigner { KeyType = client.Signer.JwsAlg, KeyExport = client.Signer.Export(), }; AccountSigner = accountKey; await File.WriteAllTextAsync(AccountPath, JsonConvert.SerializeObject(account)); } catch (Exception ex) { _log.Error(ex, "Error saving account"); account = null; } } } return(account); }
/// <summary> /// Determine if the renewal should be executes /// </summary> /// <param name="renewal"></param> /// <param name="runLevel"></param> /// <returns></returns> public async Task <RenewResult> HandleRenewal(Renewal renewal, RunLevel runLevel) { _input.CreateSpace(); _log.Reset(); using var ts = _scopeBuilder.Target(_container, renewal, runLevel); using var es = _scopeBuilder.Execution(ts, renewal, runLevel); // Generate the target var targetPlugin = es.Resolve <ITargetPlugin>(); var(disabled, disabledReason) = targetPlugin.Disabled; if (disabled) { return(new RenewResult($"Source plugin is not available. {disabledReason}")); } var target = await targetPlugin.Generate(); if (target is INull) { return(new RenewResult($"Source plugin did not generate source")); } // Create one or more orders based on the target var orderPlugin = es.Resolve <IOrderPlugin>(); var orders = orderPlugin.Split(renewal, target); if (orders == null || !orders.Any()) { return(new RenewResult("Order plugin failed to create order(s)")); } _log.Verbose("Source converted into {n} order(s)", orders.Count()); foreach (var order in orders) { if (!order.Target.IsValid(_log)) { return(new RenewResult($"Source plugin generated invalid source")); } } /// Start to check the renewal var result = await HandleOrders(es, renewal, orders.ToList(), runLevel); // Configure task scheduler var setupTaskScheduler = _args.SetupTaskScheduler; if (!setupTaskScheduler && !_args.NoTaskScheduler) { setupTaskScheduler = result.Success == true && !result.Abort && (renewal.New || renewal.Updated); } if (setupTaskScheduler && runLevel.HasFlag(RunLevel.Test)) { setupTaskScheduler = await _input.PromptYesNo($"[--test] Do you want to automatically renew with these settings?", true); if (!setupTaskScheduler) { result.Abort = true; } } if (setupTaskScheduler) { var taskLevel = runLevel; if (_args.SetupTaskScheduler) { taskLevel |= RunLevel.ForceRenew; } await es.Resolve <TaskSchedulerService>().EnsureTaskScheduler(runLevel); } return(result); }
/// <summary> /// Determine if the renewal should be executes /// </summary> /// <param name="renewal"></param> /// <param name="runLevel"></param> /// <returns></returns> public async Task <RenewResult> HandleRenewal(Renewal renewal, RunLevel runLevel) { _input.CreateSpace(); _log.Reset(); using var ts = _scopeBuilder.Target(_container, renewal, runLevel); using var es = _scopeBuilder.Execution(ts, renewal, runLevel); // Generate the target var targetPlugin = es.Resolve <ITargetPlugin>(); var(disabled, disabledReason) = targetPlugin.Disabled; if (disabled) { return(new RenewResult($"Source plugin is not available. {disabledReason}")); } var target = await targetPlugin.Generate(); if (target is INull) { return(new RenewResult($"Source plugin did not generate source")); } if (!target.IsValid(_log)) { return(new RenewResult($"Source plugin generated invalid source")); } // Check if our validation plugin is (still) up to the task var validationPlugin = es.Resolve <IValidationPluginOptionsFactory>(); if (!validationPlugin.CanValidate(target)) { return(new RenewResult($"Validation plugin is unable to validate the source. A wildcard host was introduced into a HTTP validated renewal.")); } // Create one or more orders based on the target var orderPlugin = es.Resolve <IOrderPlugin>(); var orders = orderPlugin.Split(renewal, target); if (orders == null || !orders.Any()) { return(new RenewResult("Order plugin failed to create order(s)")); } _log.Verbose("Targeted convert into {n} order(s)", orders.Count()); // Test if this renewal should run right now if (!ShouldRun(es, orders, renewal, runLevel)) { return(new RenewResult() { Abort = true }); } // If at this point we haven't retured already with an error/abort // actually execute the renewal var preScript = _settings.Execution?.DefaultPreExecutionScript; var scriptClient = es.Resolve <ScriptClient>(); if (!string.IsNullOrWhiteSpace(preScript)) { await scriptClient.RunScript(preScript, $"{renewal.Id}"); } var result = await ExecuteRenewal(es, orders.ToList(), runLevel); var postScript = _settings.Execution?.DefaultPostExecutionScript; if (!string.IsNullOrWhiteSpace(postScript)) { await scriptClient.RunScript(postScript, $"{renewal.Id}"); } // Configure task scheduler var setupTaskScheduler = _args.SetupTaskScheduler; if (!setupTaskScheduler && !_args.NoTaskScheduler) { setupTaskScheduler = result.Success && !result.Abort && (renewal.New || renewal.Updated); } if (setupTaskScheduler && runLevel.HasFlag(RunLevel.Test)) { setupTaskScheduler = await _input.PromptYesNo($"[--test] Do you want to automatically renew with these settings?", true); if (!setupTaskScheduler) { result.Abort = true; } } if (setupTaskScheduler) { var taskLevel = runLevel; if (_args.SetupTaskScheduler) { taskLevel |= RunLevel.ForceRenew; } await es.Resolve <TaskSchedulerService>().EnsureTaskScheduler(runLevel); } return(result); }