public async Task Aquire(IAzureOptionsCommon options, IInputService _input) { var defaultEnvironment = (await Environment.GetValue()) ?? AzureEnvironments.AzureCloud; var environments = new List <Choice <Func <Task> > >( AzureEnvironments.ResourceManagerUrls .OrderBy(kvp => kvp.Key) .Select(kvp => Choice.Create <Func <Task> >(() => { options.AzureEnvironment = kvp.Key; return(Task.CompletedTask); }, description: kvp.Key, @default: kvp.Key == defaultEnvironment))) { Choice.Create <Func <Task> >(async() => await InputUrl(_input, options), "Use a custom resource manager url") }; var chosen = await _input.ChooseFromMenu("Which Azure environment are you using?", environments); await chosen.Invoke(); options.UseMsi = await UseMsi.GetValue() == true || await _input.PromptYesNo("Do you want to use a managed service identity?", false); if (!options.UseMsi) { // These options are only necessary for client id/secret authentication. options.TenantId = await TenantId.Interactive(_input, "Directory/tenant id").GetValue(); options.ClientId = await ClientId.Interactive(_input, "Application client id").GetValue(); options.Secret = await ClientSecret.Interactive(_input, "Application client secret").GetValue(); } }
public override async Task <CertificateStoreOptions?> Aquire(IInputService inputService, RunLevel runLevel) { var ret = await Default(); if (ret != null && string.IsNullOrEmpty(ret.StoreName) && runLevel.HasFlag(RunLevel.Advanced)) { var currentDefault = CertificateStore.DefaultStore(_settingsService, _iisClient); var choices = new List <Choice <string?> >(); if (_iisClient.Version.Major > 8) { choices.Add(Choice.Create <string?>( "WebHosting", description: "[WebHosting] - Dedicated store for IIS")); } choices.Add(Choice.Create <string?>( "My", description: "[My] - General computer store (for Exchange/RDS)")); choices.Add(Choice.Create <string?>( null, description: $"[Default] - Use global default, currently {currentDefault}", @default: true)); var choice = await inputService.ChooseFromMenu( "Choose store to use, or type the name of another unlisted store", choices, other => Choice.Create <string?>(other)); // final save ret.StoreName = string.IsNullOrWhiteSpace(choice) ? null : choice; } return(ret); }
public async Task Aquire(IAzureOptionsCommon options) { var az = _arguments.GetArguments <T>(); var environments = new List <Choice <Func <Task> > >( AzureEnvironments.ResourceManagerUrls .OrderBy(kvp => kvp.Key) .Select(kvp => Choice.Create <Func <Task> >(() => { options.AzureEnvironment = kvp.Key; return(Task.CompletedTask); }, description: kvp.Key, @default: kvp.Key == AzureEnvironments.AzureCloud))) { Choice.Create <Func <Task> >(async() => await InputUrl(_input, options), "Use a custom resource manager url") }; var chosen = await _input.ChooseFromMenu("Which Azure environment are you using?", environments); await chosen.Invoke(); options.UseMsi = az.AzureUseMsi || await _input.PromptYesNo("Do you want to use a managed service identity?", true); if (!options.UseMsi) { // These options are only necessary for client id/secret authentication. options.TenantId = await _arguments.TryGetArgument(az.AzureTenantId, _input, "Directory/tenant id"); options.ClientId = await _arguments.TryGetArgument(az.AzureClientId, _input, "Application client id"); options.Secret = new ProtectedString(await _arguments.TryGetArgument(az.AzureSecret, _input, "Application client secret", true)); } }
public override async Task <ScriptOptions?> Aquire(Target target, IInputService input, RunLevel runLevel) { var args = _arguments.GetArguments <ScriptArguments>(); var ret = new ScriptOptions(); string?createScript = null; do { createScript = args?.DnsCreateScript ?? await input.RequestString("Path to script that creates DNS records"); }while (!createScript.ValidFile(_log)); string?deleteScript = null; var chosen = await input.ChooseFromMenu( "How to delete records after validation", new List <Choice <Func <Task> > >() { Choice.Create <Func <Task> >(() => { deleteScript = createScript; return(Task.CompletedTask); }, "Using the same script"), Choice.Create <Func <Task> >(async() => { do { deleteScript = args?.DnsDeleteScript ?? await input.RequestString("Path to script that deletes DNS records"); }while (!deleteScript.ValidFile(_log)); }, "Using a different script"), Choice.Create <Func <Task> >(() => Task.CompletedTask, "Do not delete") }); await chosen.Invoke(); ProcessScripts(ret, null, createScript, deleteScript); input.Show("{Identifier}", "Domain that's being validated"); input.Show("{RecordName}", "Full TXT record name"); input.Show("{Token}", "Expected value in the TXT record"); var createArgs = args?.DnsCreateScriptArguments ?? await input.RequestString($"Input parameters for create script, or enter for default \"{Script.DefaultCreateArguments}\""); string?deleteArgs = null; if (!string.IsNullOrWhiteSpace(ret.DeleteScript) || !string.IsNullOrWhiteSpace(ret.Script)) { deleteArgs = args?.DnsDeleteScriptArguments ?? await input.RequestString($"Input parameters for delete script, or enter for default \"{Script.DefaultDeleteArguments}\""); } ProcessArgs(ret, createArgs, deleteArgs); return(ret); }
/// <summary> /// Main user experience /// </summary> private async Task MainMenu() { var options = new List <Choice <Func <Task> > > { Choice.Create <Func <Task> >( () => _renewalManager.SetupRenewal(RunLevel.Interactive | RunLevel.Simple), "Create new certificate (simple for IIS)", "N", @default: _userRoleService.AllowIIS, disabled: !_userRoleService.AllowIIS), Choice.Create <Func <Task> >( () => _renewalManager.SetupRenewal(RunLevel.Interactive | RunLevel.Advanced), "Create new certificate (full options)", "M", @default: !_userRoleService.AllowIIS), Choice.Create <Func <Task> >( () => _renewalManager.ShowRenewals(), "List scheduled renewals", "L"), Choice.Create <Func <Task> >( () => _renewalManager.CheckRenewals(RunLevel.Interactive), "Renew scheduled", "R"), Choice.Create <Func <Task> >( () => _renewalManager.RenewSpecific(), "Renew specific", "S"), Choice.Create <Func <Task> >( () => _renewalManager.CheckRenewals(RunLevel.Interactive | RunLevel.ForceRenew), "Renew *all*", "A"), Choice.Create <Func <Task> >( () => ExtraMenu(), "More options...", "O"), Choice.Create <Func <Task> >( () => { _args.CloseOnFinish = true; _args.Test = false; return(Task.CompletedTask); }, "Quit", "Q") }; var chosen = await _input.ChooseFromMenu("Please choose from the menu", options); await chosen.Invoke(); }
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> /// 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> /// 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.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.", true); 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, runLevel, filtered); input.Show(null, "You may either choose to include all listed bindings as host names in your certificate, " + "or apply an additional filter. Different types of filters are available.", true); var askExclude = true; var filters = new List <Choice <Func <Task> > > { Choice.Create <Func <Task> >(() => { askExclude = false; return(InputHosts( "Include bindings", input, allBindings, filtered, options, () => options.IncludeHosts, x => options.IncludeHosts = x)); }, "Pick specific bindings from the list"), Choice.Create <Func <Task> >(() => { return(InputPattern(input, options)); }, "Pick bindings based on a search pattern"), Choice.Create <Func <Task> >(() => { askExclude = false; return(Task.CompletedTask); }, "Pick *all* bindings", @default: true) }; if (runLevel.HasFlag(RunLevel.Advanced)) { filters.Insert(2, Choice.Create <Func <Task> >(() => { askExclude = true; return(InputRegex(input, options)); }, "Pick bindings based on a regular expression")); } var chosen = await input.ChooseFromMenu("How do you want to pick the bindings?", filters); await chosen.Invoke(); filtered = _iisHelper.FilterBindings(allBindings, options); // Check for wildcards in simple mode if (!runLevel.HasFlag(RunLevel.Advanced) && filtered.Any(x => x.Wildcard)) { await ListBindings(input, runLevel, filtered); input.Show(null, "The pattern that you've chosen matches a wildcard binding, which " + "is not supported by the 'simple' mode of this program because it requires DNS " + "validation. Please try again with a different pattern or use the 'full options' " + "mode instead.", true); return(null); } // Exclude specific bindings var listForCommon = false; if (askExclude && filtered.Count > 1 && runLevel.HasFlag(RunLevel.Advanced)) { await ListBindings(input, runLevel, filtered); 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.", true); await InputHosts("Exclude bindings", input, allBindings, filtered, options, () => options.ExcludeHosts, x => options.ExcludeHosts = x); if (options.ExcludeHosts != null) { filtered = _iisHelper.FilterBindings(allBindings, options); listForCommon = true; } } else { listForCommon = true; } // Now the common name if (filtered.Select(x => x.HostUnicode).Distinct().Count() > 1) { // If no bindings have been excluded, we can re-use // the previously printed list if (listForCommon) { await ListBindings(input, runLevel, filtered); } await InputCommonName(input, filtered, options); } return(options); }
/// <summary> /// Get a secret from interactive mode setup /// </summary> /// <param name="purpose"></param> /// <returns></returns> public async Task <string?> GetSecret(string purpose, string? @default = null, string?none = null, bool required = false, bool multiline = false) { var stop = false; string?ret = null; // While loop allows the "Find in vault" option // to be cancelled so that the user can still // input a new password if it's not found yet // without having to restart the process. while (!stop && string.IsNullOrEmpty(ret)) { var options = new List <Choice <Func <Task <string?> > > >(); if (!required) { options.Add(Choice.Create <Func <Task <string?> > >( () => { stop = true; return(Task.FromResult(none)); }, description: "None")); } options.Add(Choice.Create <Func <Task <string?> > >( async() => { stop = true; if (multiline) { return(await _inputService.RequestString(purpose, true)); } else { return(await _inputService.ReadPassword(purpose)); } }, description: "Type/paste in console")); options.Add(Choice.Create <Func <Task <string?> > >( () => FindSecret(), description: "Search in vault")); if (!string.IsNullOrWhiteSpace(@default)) { options.Add(Choice.Create <Func <Task <string?> > >( () => { stop = true; return(Task.FromResult <string?>(@default)); }, description: "Default")); } // Handle undefined input as direct password Choice <Func <Task <string?> > > processUnkown(string?unknown) => Choice.Create <Func <Task <string?> > >(() => Task.FromResult(unknown)); var chosen = await _inputService.ChooseFromMenu("Choose from the menu", options, (x) => processUnkown(x)); ret = await chosen.Invoke(); } if (ret == none) { return(none); } if (ret == @default || ret == null) { return(@default); } // Offer to save in list if (!ret.StartsWith(VaultPrefix)) { var save = await _inputService.PromptYesNo($"Save to vault for future reuse?", false); if (save) { return(await ChooseKeyAndStoreSecret(ret)); } } return(ret); }
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> /// Get a secret from interactive mode setup /// </summary> /// <param name="purpose"></param> /// <returns></returns> public async Task <string?> GetSecret(string purpose, string? @default = null, bool?multiline = null) { var stop = false; string?ret = null; while (!stop && string.IsNullOrEmpty(ret)) { var options = new List <Choice <Func <Task <string?> > > > { Choice.Create <Func <Task <string?> > >( () => { stop = true; return(Task.FromResult <string?>(null)); }, description: "None"), Choice.Create <Func <Task <string?> > >( async() => { if (multiline == true) { return(await _inputService.RequestString("New secret", true)); } else { return(await _inputService.ReadPassword("New secret")); } }, description: "Type/paste in console"), Choice.Create <Func <Task <string?> > >( () => FindSecret(), description: "Search in vault"), }; if (!string.IsNullOrWhiteSpace(@default)) { options.Add(Choice.Create <Func <Task <string?> > >( () => { stop = true; return(Task.FromResult <string?>(@default)); }, description: "Default")); } var chosen = await _inputService.ChooseFromMenu(purpose, options); ret = await chosen.Invoke(); } if (stop || string.IsNullOrWhiteSpace(ret)) { return(null); } // Offer to save in list if (ret != @default && !ret.StartsWith(VaultPrefix)) { var save = await _inputService.PromptYesNo($"Save to vault for future reuse?", false); if (save) { return(await ChooseKeyAndStoreSecret(ret)); } } return(ret); }
/// <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; do { var all = selectedRenewals.Count() == originalSelection.Count(); var none = selectedRenewals.Count() == 0; var totalLabel = originalSelection.Count() != 1 ? "renewals" : "renewal"; var selectionLabel = all ? $"*all* renewals" : none ? "no renewals" : $"{selectedRenewals.Count()} of {originalSelection.Count()} {totalLabel}"; var renewalSelectedLabel = selectedRenewals.Count() != 1 ? "renewals" : "renewal"; _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.", true); await _input.WritePagedList( selectedRenewals.Select(x => Choice.Create <Renewal?>(x, description: x.ToString(_input), color: x.History.Last().Success ? x.IsDue() ? ConsoleColor.DarkYellow : ConsoleColor.Green : ConsoleColor.Red))); var options = new List <Choice <Func <Task> > >(); options.Add( Choice.Create <Func <Task> >( async() => selectedRenewals = await FilterRenewalsMenu(selectedRenewals), all ? "Apply filter" : "Apply additional filter", "F", @disabled: selectedRenewals.Count() < 2, disabledReason: "Not enough renewals to filter.", @default: !(selectedRenewals.Count() < 2))); options.Add( Choice.Create <Func <Task> >( async() => selectedRenewals = await SortRenewalsMenu(selectedRenewals), "Sort renewals", "S", @disabled: selectedRenewals.Count() < 2, disabledReason: "Not enough renewals to sort.")); options.Add( Choice.Create <Func <Task> >( () => { selectedRenewals = originalSelection; return(Task.CompletedTask); }, "Reset sorting and filtering", "X", @disabled: all, disabledReason: "No filters have been applied yet.", @default: originalSelection.Count() > 0 && none)); 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, disabledReason: "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, disabledReason: "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, disabledReason: "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) { foreach (var renewal in selectedRenewals) { using var scope = _scopeBuilder.Execution(_container, renewal, RunLevel.Interactive); var cs = scope.Resolve <ICertificateService>(); try { await cs.RevokeCertificate(renewal); renewal.History.Add(new RenewResult("Certificate revoked")); } catch (Exception ex) { _exceptionHandler.HandleException(ex); } } ; } }, $"Revoke {selectionLabel}", "V", @disabled: none, disabledReason: "No renewals selected.")); options.Add( Choice.Create <Func <Task> >( () => { quit = true; return(Task.CompletedTask); }, "Back", "Q", @default: originalSelection.Count() == 0)); _input.Show(null, $"Currently selected {selectedRenewals.Count()} of {originalSelection.Count()} {totalLabel}", true); var chosen = await _input.ChooseFromMenu("Please choose from the menu", options); await chosen.Invoke(); }while (!quit); }